diff --git a/lib/controllers/git-panel-controller.js b/lib/controllers/git-panel-controller.js index 424d71aba1..40c654ee61 100644 --- a/lib/controllers/git-panel-controller.js +++ b/lib/controllers/git-panel-controller.js @@ -15,6 +15,7 @@ export default class GitPanelController { this.pull = this.pull.bind(this) this.fetch = this.fetch.bind(this) this.commit = this.commit.bind(this) + this.setAmending = this.setAmending.bind(this) this.checkout = this.checkout.bind(this) this.abortMerge = this.abortMerge.bind(this) this.switchRepository(props.repository) @@ -47,7 +48,9 @@ export default class GitPanelController { stageFiles={this.stageFiles} unstageFiles={this.unstageFiles} commit={this.commit} + setAmending={this.setAmending} isMerging={this.isMerging} + isAmending={this.isAmending} abortMerge={this.abortMerge} push={this.push} pull={this.pull} @@ -87,7 +90,7 @@ export default class GitPanelController { async _refreshModelData (repository) { if (repository) { this.unstagedChanges = await repository.getUnstagedChanges() - this.stagedChanges = await repository.getStagedChanges() + await this._refreshStagedChanges(repository) this.mergeConflicts = await repository.getMergeConflicts() this.lastCommit = await repository.getLastCommit() this.isMerging = await repository.isMerging() @@ -111,6 +114,14 @@ export default class GitPanelController { return etch.update(this) } + async _refreshStagedChanges (repository) { + if (this.isAmending) { + this.stagedChanges = await repository.getStagedChangesSinceParentCommit() + } else { + this.stagedChanges = await repository.getStagedChanges() + } + } + unstageFilePatch (filePatch) { return this.repository.applyPatchToIndex(filePatch.getUnstagePatch()) } @@ -132,7 +143,11 @@ export default class GitPanelController { } unstageFiles (filePaths) { - return this.repository.unstageFiles(filePaths) + if (this.isAmending) { + return this.repository.stageFilesFromParentCommit(filePaths) + } else { + return this.repository.unstageFiles(filePaths) + } } push () { @@ -155,8 +170,16 @@ export default class GitPanelController { return this.stagedChanges.length === 0 && this.unstagedChanges.length === 0 } - commit (message, options) { - return this.repository.commit(message, options) + async commit (message, options) { + await this.repository.commit(message, options) + this.setAmending(false) + } + + async setAmending (isAmending) { + if (this.props.didChangeAmending) this.props.didChangeAmending() + this.isAmending = isAmending + await this._refreshStagedChanges(this.repository) + return etch.update(this) } abortMerge () { diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 9811afa976..b042f7f5eb 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -45,9 +45,9 @@ export default class GitShellOutStrategy { return this.exec(args) } - unstageFiles (paths) { + unstageFiles (paths, commit = 'HEAD') { if (paths.length === 0) return - const args = ['reset', '--'].concat(paths) + const args = ['reset', commit, '--' ].concat(paths) return this.exec(args) } @@ -98,6 +98,7 @@ export default class GitShellOutStrategy { async diff (options = {}) { let args = ['diff', '--no-prefix', '--no-renames', '--diff-filter=u'] if (options.staged) args.push('--staged') + if (options.baseCommit) args.push(options.baseCommit) let output = await this.exec(args) let rawDiffs = [] diff --git a/lib/github-package.js b/lib/github-package.js index 4836e77b10..01fdbf7846 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -28,7 +28,8 @@ export default class GithubPackage { commandRegistry, notificationManager, didSelectFilePatch: this.didSelectFilePatch.bind(this), - didSelectMergeConflictFile: this.didSelectMergeConflictFile.bind(this) + didSelectMergeConflictFile: this.didSelectMergeConflictFile.bind(this), + didChangeAmending: this.didChangeAmending.bind(this) }) this.changedFilesCountController = new ChangedFilesCountController({ repository: null, didClick: this.toggleGitPanel.bind(this) @@ -97,6 +98,13 @@ export default class GithubPackage { } } + didChangeAmending () { + if (this.filePatchController && this.filePatchController.props.stagingStatus === 'staged') { + this.filePatchController.destroy() + this.filePatchController = null + } + } + toggleGitPanel () { if (this.workspaceGitPanel) { this.workspaceGitPanel.destroy() diff --git a/lib/helpers.js b/lib/helpers.js index fc5bf690e5..7f6baa9921 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -38,6 +38,10 @@ export function fsStat (absoluteFilePath) { }) } +export function shortenSha (sha) { + return sha.slice(0, 8) +} + export const classNameForStatus = { added: 'added', deleted: 'removed', diff --git a/lib/models/repository.js b/lib/models/repository.js index d83ac36080..6d6bb32974 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -45,6 +45,7 @@ export default class Repository { this.workingDirectory = workingDirectory this.emitter = new Emitter() this.stagedFilePatchesByPath = new Map() + this.stagedFilePatchesSinceParentCommitByPath = new Map() this.unstagedFilePatchesByPath = new Map() this.mergeConflictsByPath = new Map() this.git = gitStrategy @@ -76,86 +77,94 @@ export default class Repository { async refresh () { if (global.PRINT_GIT_TIMES) console.time('refresh') - await this.refreshStagedChanges() - await this.refreshUnstagedChanges() - await this.refreshMergeConflicts() + this.stagedChangesPromise = this.fetchStagedChanges() + this.stagedChangesSinceParentCommitPromise = this.fetchStagedChangesSinceParentCommit() + this.unstagedChangesPromise = this.fetchUnstagedChanges() + this.mergeConflictsPromise = this.fetchMergeConflicts() + await Promise.all([ + this.stagedChangesPromise, + this.stagedChangesSinceParentCommitPromise, + this.unstagedChangesPromise, + this.mergeConflictsPromise + ]) if (global.PRINT_GIT_TIMES) console.timeEnd('refresh') this.didUpdate() } getUnstagedChanges () { - return this.unstagedChangesPromise || this.refreshUnstagedChanges() + if (!this.unstagedChangesPromise) { + this.unstagedChangesPromise = this.fetchUnstagedChanges() + } + return this.unstagedChangesPromise } getStagedChanges () { - return this.stagedChangesPromise || this.refreshStagedChanges() + if (!this.stagedChangesPromise) { + this.stagedChangesPromise = this.fetchStagedChanges() + } + return this.stagedChangesPromise } - getMergeConflicts () { - return this.mergeConflictsPromise || this.refreshMergeConflicts() + getStagedChangesSinceParentCommit () { + if (!this.stagedChangesSinceParentCommitPromise) { + this.stagedChangesSinceParentCommitPromise = this.fetchStagedChangesSinceParentCommit() + } + return this.stagedChangesSinceParentCommitPromise } - refreshUnstagedChanges () { - this.unstagedChangesPromise = this.fetchUnstagedChanges() - return this.unstagedChangesPromise + getMergeConflicts () { + if (!this.mergeConflictsPromise) { + this.mergeConflictsPromise = this.fetchMergeConflicts() + } + return this.mergeConflictsPromise } - refreshStagedChanges () { - this.stagedChangesPromise = this.fetchStagedChanges() - return this.stagedChangesPromise + async fetchUnstagedChanges () { + const rawDiffs = await this.git.diff() + return this.updateFilePatches(this.unstagedFilePatchesByPath, rawDiffs) } - refreshMergeConflicts () { - this.mergeConflictsPromise = this.fetchMergeConflicts() - return this.mergeConflictsPromise + async fetchStagedChanges () { + const rawDiffs = await this.git.diff({staged: true}) + return this.updateFilePatches(this.stagedFilePatchesByPath, rawDiffs) } - async fetchUnstagedChanges () { - const rawDiffs = await this.git.diff() - const validFilePatches = new Set() - for (let newPatch of this.buildFilePatchesFromRawDiffs(rawDiffs)) { - const path = newPatch.getPath() - const existingPatch = this.unstagedFilePatchesByPath.get(path) - if (existingPatch == null) { - this.unstagedFilePatchesByPath.set(path, newPatch) + async fetchStagedChangesSinceParentCommit () { + try { + const rawDiffs = await this.git.diff({staged: true, baseCommit: 'HEAD~'}) + return this.updateFilePatches(this.stagedFilePatchesSinceParentCommitByPath, rawDiffs) + } catch (e) { + if (e.message.includes(`ambiguous argument 'HEAD~'`)) { + return [] } else { - existingPatch.update(newPatch) - } - validFilePatches.add(path) - } - - for (let [path, patch] of this.unstagedFilePatchesByPath) { - if (!validFilePatches.has(path)) { - this.unstagedFilePatchesByPath.delete(path) - patch.destroy() + throw e } } - return Array.from(this.unstagedFilePatchesByPath.values()) - .sort((a, b) => a.getPath().localeCompare(b.getPath())) } - async fetchStagedChanges () { - const rawDiffs = await this.git.diff({staged: true}) + updateFilePatches (patchesByPath, rawDiffs) { + const filePatches = this.buildFilePatchesFromRawDiffs(rawDiffs) + const validFilePatches = new Set() - for (let newPatch of this.buildFilePatchesFromRawDiffs(rawDiffs)) { + for (let newPatch of filePatches) { const path = newPatch.getPath() - const existingPatch = this.stagedFilePatchesByPath.get(path) + const existingPatch = patchesByPath.get(path) if (existingPatch == null) { - this.stagedFilePatchesByPath.set(path, newPatch) + patchesByPath.set(path, newPatch) } else { existingPatch.update(newPatch) } validFilePatches.add(path) } - for (let [path, patch] of this.stagedFilePatchesByPath) { + for (let [path, patch] of patchesByPath) { if (!validFilePatches.has(path)) { - this.stagedFilePatchesByPath.delete(path) + patchesByPath.delete(path) patch.destroy() } } - return Array.from(this.stagedFilePatchesByPath.values()) - .sort((a, b) => a.getPath().localeCompare(b.getPath())) + + return Array.from(patchesByPath.values()).sort((a, b) => a.getPath().localeCompare(b.getPath())) } buildFilePatchesFromRawDiffs (rawDiffs) { @@ -228,6 +237,11 @@ export default class Repository { await this.refresh() } + async stageFilesFromParentCommit (paths) { + await this.git.unstageFiles(paths, 'HEAD~') + await this.refresh() + } + async applyPatchToIndex (filePatch) { const patchStr = filePatch.getHeaderString() + filePatch.toString() await this.git.applyPatchToIndex(patchStr) @@ -268,7 +282,8 @@ export default class Repository { async commit (message, options) { await this.git.commit(this.stripCommitComments(message), options) - this.refreshStagedChanges() + this.stagedChangesPromise = null + this.stagedChangesSinceParentCommitPromise = null this.didUpdate() } diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 911a18b732..9f3b1b2788 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -3,6 +3,7 @@ import {CompositeDisposable} from 'atom' import etch from 'etch' +import {shortenSha} from '../helpers' export default class CommitView { constructor (props) { @@ -64,7 +65,7 @@ export default class CommitView { + disabled={!this.isCommitButtonEnabled()}>{this.commitButtonText()}
{this.getRemainingCharacters()}
@@ -92,18 +93,30 @@ export default class CommitView { return etch.update(this) } - handleAmendBoxClick () { + async handleAmendBoxClick () { const checked = this.refs.amend.checked - if (this.editor.getText()) { - const choice = atom.confirm({ - message: 'Would you like to discard the current commit message?', - detailedMessage: checked ? 'It will be replaced with the most recent commit message' : '', - buttons: ['Replace', 'Keep current'] - }) - if (choice !== 0) return + const currentMessage = this.editor.getText() + const lastCommitMessage = this.props.lastCommit ? this.props.lastCommit.message : '' + + if (this.props.setAmending) await this.props.setAmending(checked) + + if (checked) { + if (currentMessage && currentMessage !== lastCommitMessage) { + const choice = atom.confirm({ + message: 'Would you like to discard the current commit message?', + detailedMessage: checked ? 'It will be replaced with the most recent commit message' : '', + buttons: ['Keep current', 'Replace'] + }) + if (choice !== 0) { + this.editor.setText(lastCommitMessage) + } + } else { + this.editor.setText(lastCommitMessage) + } + } else if (currentMessage === lastCommitMessage) { + this.editor.setText('') } - const text = (checked && this.props.lastCommit) ? this.props.lastCommit.message : '' - this.editor.setText(text) + return etch.update(this) } @@ -138,4 +151,16 @@ export default class CommitView { isCommitButtonEnabled () { return this.props.stagedChangesExist && !this.props.mergeConflictsExist && this.editor && this.editor.getText().length !== 0 } + + commitButtonText () { + if (this.props.isAmending) { + return `Amend commit ${shortenSha(this.props.lastCommit.sha)})` + } else { + if (this.props.branchName) { + return `Commit to ${this.props.branchName}` + } else { + return 'Commit' + } + } + } } diff --git a/lib/views/git-panel-view.js b/lib/views/git-panel-view.js index 833d6de544..4569128189 100644 --- a/lib/views/git-panel-view.js +++ b/lib/views/git-panel-view.js @@ -52,6 +52,8 @@ export default class GitPanelView { didSelectMergeConflictFile={this.props.didSelectMergeConflictFile} stageFiles={this.props.stageFiles} unstageFiles={this.props.unstageFiles} + lastCommit={this.props.lastCommit} + isAmending={this.props.isAmending} /> 0} mergeConflictsExist={this.props.mergeConflicts.length > 0} commit={this.props.commit} + setAmending={this.props.setAmending} disableCommitButton={this} abortMerge={this.props.abortMerge} branchName={this.props.branchName} @@ -67,6 +70,7 @@ export default class GitPanelView { maximumCharacterLimit={72} message={this.props.mergeMessage} isMerging={this.props.isMerging} + isAmending={this.props.isAmending} lastCommit={this.props.lastCommit} /> diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 2699ee88f3..2bda299b7c 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -10,6 +10,7 @@ import MergeConflict from '../models/merge-conflict' import FilePatchListItemView from './file-patch-list-item-view' import MergeConflictListItemView from './merge-conflict-list-item-view' import MultiListCollection from '../multi-list-collection' +import {shortenSha} from '../helpers' export const ListTypes = { STAGED: Symbol('LIST_STAGED'), @@ -220,7 +221,14 @@ export default class StagingView {
- Staged Changes + + Staged Changes + { + this.props.isAmending ? + ` (amending ${shortenSha(this.props.lastCommit.sha)})` : + '' + } +
this.selectList(ListTypes.STAGED, {suppressCallback: true})} diff --git a/test/controllers/changed-files-count-controller.test.js b/test/controllers/changed-files-count-controller.test.js index baf94e7829..8a6f29005d 100644 --- a/test/controllers/changed-files-count-controller.test.js +++ b/test/controllers/changed-files-count-controller.test.js @@ -22,11 +22,13 @@ describe('ChangedFilesCountController', () => { fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'a change\n') fs.unlinkSync(path.join(workdirPath, 'b.txt')) - const [patchToStage] = await repository.refreshUnstagedChanges() + repository.refresh() + + const [patchToStage] = await repository.getUnstagedChanges() await repository.applyPatchToIndex(patchToStage) await view.lastModelDataRefreshPromise - assert.deepEqual(view.refs.changedFilesCount.props.stagedChanges, await repository.refreshStagedChanges()) - assert.deepEqual(view.refs.changedFilesCount.props.unstagedChanges, await repository.refreshUnstagedChanges()) + assert.deepEqual(view.refs.changedFilesCount.props.stagedChanges, await repository.getStagedChanges()) + assert.deepEqual(view.refs.changedFilesCount.props.unstagedChanges, await repository.getUnstagedChanges()) view.refs.changedFilesCount.props.didClick() assert(didClick.calledOnce) diff --git a/test/controllers/git-panel-controller.test.js b/test/controllers/git-panel-controller.test.js index 12b09e0727..f340a3bea0 100644 --- a/test/controllers/git-panel-controller.test.js +++ b/test/controllers/git-panel-controller.test.js @@ -56,6 +56,27 @@ describe('GitPanelController', () => { assert.equal(controller.refs.gitPanel.props.unstagedChanges, await repository2.getUnstagedChanges()) }) + it('displays the staged changes since the parent commmit when amending', async function () { + const didChangeAmending = sinon.spy() + const workdirPath = await cloneRepository('multiple-commits') + const repository = await buildRepository(workdirPath) + const controller = new GitPanelController({workspace, commandRegistry, repository, didChangeAmending}) + await controller.lastModelDataRefreshPromise + assert.deepEqual(controller.refs.gitPanel.props.stagedChanges, []) + assert.equal(didChangeAmending.callCount, 0) + + await controller.setAmending(true) + assert.equal(didChangeAmending.callCount, 1) + assert.deepEqual( + controller.refs.gitPanel.props.stagedChanges, + await controller.repository.getStagedChangesSinceParentCommit() + ) + + await controller.commit('Delete most of the code', {amend: true}) + await controller.lastModelDataRefreshPromise + assert(!controller.refs.gitPanel.props.isAmending) + }) + describe('integration tests', () => { it('can stage and unstage files and commit', async () => { const workdirPath = await cloneRepository('three-files') diff --git a/test/github-package.test.js b/test/github-package.test.js index a9554fa7ec..83e13a6b9b 100644 --- a/test/github-package.test.js +++ b/test/github-package.test.js @@ -214,6 +214,24 @@ describe('GithubPackage', () => { }) }) + describe('when amend mode is toggled in the staging panel while viewing a staged change', () => { + it('closes the file patch pane item', async () => { + const workdirPath = await cloneRepository('three-files') + const repository = await buildRepository(workdirPath) + + githubPackage.getActiveRepository = function () { return repository } + const hunk = new Hunk(1, 1, 1, 3, []) + const filePatch = new FilePatch('a.txt', 'a.txt', 'modified', [hunk]) + + githubPackage.gitPanelController.props.didSelectFilePatch(filePatch, 'staged') + assert.equal(workspace.getActivePaneItem(), githubPackage.filePatchController) + + githubPackage.gitPanelController.props.didChangeAmending() + assert.isNull(githubPackage.filePatchController) + assert.isUndefined(workspace.getActivePaneItem()) + }) + }) + describe('when the changed files label in the status bar is clicked', () => { it('toggles the git panel', async () => { const workdirPath = await cloneRepository('three-files') diff --git a/test/models/repository.test.js b/test/models/repository.test.js index c4ea3397b9..aaafa2556b 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -8,7 +8,7 @@ import sinon from 'sinon' import {cloneRepository, buildRepository, assertDeepPropertyVals, setUpLocalAndRemoteRepositories, getHeadCommitOnRemote} from '../helpers' describe('Repository', function () { - describe('refreshing staged and unstaged changes', () => { + describe('getting staged and unstaged changes', () => { it('returns a promise resolving to an array of FilePatch objects', async () => { const workingDirPath = await cloneRepository('three-files') fs.writeFileSync(path.join(workingDirPath, 'a.txt'), 'qux\nfoo\nbar\n', 'utf8') @@ -17,7 +17,7 @@ describe('Repository', function () { fs.writeFileSync(path.join(workingDirPath, 'e.txt'), 'qux', 'utf8') const repo = await buildRepository(workingDirPath) - const filePatches = await repo.refreshUnstagedChanges() + const filePatches = await repo.getUnstagedChanges() assertDeepPropertyVals(filePatches, [ { @@ -93,12 +93,14 @@ describe('Repository', function () { fs.unlinkSync(path.join(workingDirPath, 'b.txt')) fs.writeFileSync(path.join(workingDirPath, 'c.txt'), 'bar\nbaz') fs.writeFileSync(path.join(workingDirPath, 'd.txt'), 'qux', 'utf8') - const unstagedFilePatches1 = await repo.refreshUnstagedChanges() + await repo.refresh() + const unstagedFilePatches1 = await repo.getUnstagedChanges() fs.writeFileSync(path.join(workingDirPath, 'a.txt'), 'baz\nfoo\nqux', 'utf8') fs.writeFileSync(path.join(workingDirPath, 'c.txt'), 'baz') fs.unlinkSync(path.join(workingDirPath, 'd.txt')) - const unstagedFilePatches2 = await repo.refreshUnstagedChanges() + await repo.refresh() + const unstagedFilePatches2 = await repo.getUnstagedChanges() assert.equal(unstagedFilePatches1.length, 4) assert.equal(unstagedFilePatches2.length, 3) @@ -110,11 +112,11 @@ describe('Repository', function () { await repo.stageFiles([unstagedFilePatches2[0].getPath()]) await repo.stageFiles([unstagedFilePatches2[1].getPath()]) await repo.stageFiles([unstagedFilePatches2[2].getPath()]) - const stagedFilePatches1 = await repo.refreshStagedChanges() + const stagedFilePatches1 = await repo.getStagedChanges() await repo.unstageFiles([stagedFilePatches1[2].getPath()]) - const stagedFilePatches2 = await repo.refreshStagedChanges() - const unstagedFilePatches3 = await repo.refreshUnstagedChanges() + const stagedFilePatches2 = await repo.getStagedChanges() + const unstagedFilePatches3 = await repo.getUnstagedChanges() assert.equal(stagedFilePatches1.length, 3) assert.equal(stagedFilePatches2.length, 2) @@ -169,7 +171,7 @@ describe('Repository', function () { await repo.stageFiles([filePath1]) await repo.stageFiles([filePath2]) - assert.deepEqual(await repo.refreshStagedChanges(), patches) + assert.deepEqual(await repo.getStagedChanges(), patches) assert.deepEqual(await repo.getUnstagedChanges(), []) await repo.unstageFiles([filePath1]) @@ -193,6 +195,48 @@ describe('Repository', function () { assert.deepEqual(await repo.getUnstagedChanges(), [patch]) assert.deepEqual(await repo.getStagedChanges(), []) }) + + it('can unstage and retrieve staged changes relative to HEAD~', async () => { + const workingDirPath = await cloneRepository('multiple-commits') + const repo = await buildRepository(workingDirPath) + fs.writeFileSync(path.join(workingDirPath, 'file.txt'), 'three\nfour\n', 'utf8') + assertDeepPropertyVals(await repo.getStagedChangesSinceParentCommit(), [ + { + oldPath: 'file.txt', + newPath: 'file.txt', + status: 'modified', + hunks: [ + { + lines: [ + {status: 'deleted', text: 'two', oldLineNumber: 1, newLineNumber: -1}, + {status: 'added', text: 'three', oldLineNumber: -1, newLineNumber: 1}, + ] + } + ] + } + ]) + + await repo.stageFiles(['file.txt']) + assertDeepPropertyVals(await repo.getStagedChangesSinceParentCommit(), [ + { + oldPath: 'file.txt', + newPath: 'file.txt', + status: 'modified', + hunks: [ + { + lines: [ + {status: 'deleted', text: 'two', oldLineNumber: 1, newLineNumber: -1}, + {status: 'added', text: 'three', oldLineNumber: -1, newLineNumber: 1}, + {status: 'added', text: 'four', oldLineNumber: -1, newLineNumber: 2}, + ] + } + ] + } + ]) + + await repo.stageFilesFromParentCommit(['file.txt']) + assert.deepEqual(await repo.getStagedChangesSinceParentCommit(), []) + }) }) describe('applyPatchToIndex', () => { @@ -203,7 +247,8 @@ describe('Repository', function () { const [unstagedPatch1] = (await repo.getUnstagedChanges()).map(p => p.copy()) fs.writeFileSync(path.join(workingDirPath, 'subdir-1', 'a.txt'), 'qux\nfoo\nbar\nbaz\n', 'utf8') - await repo.refreshUnstagedChanges() + await repo.refresh() + await repo.getUnstagedChanges() const [unstagedPatch2] = (await repo.getUnstagedChanges()).map(p => p.copy()) await repo.applyPatchToIndex(unstagedPatch1) @@ -229,6 +274,8 @@ describe('Repository', function () { ) unstagedLines.splice(11, 2, 'this is a modified line') fs.writeFileSync(filePath, unstagedLines.join('\n')) + await repository.refresh() + const [unstagedFilePatch] = await repository.getUnstagedChanges() const unstagedListener = sinon.spy() unstagedFilePatch.onDidUpdate(unstagedListener) @@ -423,7 +470,7 @@ describe('Repository', function () { }) describe('merge conflicts', () => { - describe('refreshMergeConflicts()', () => { + describe('getMergeConflicts()', () => { it('returns a promise resolving to an array of MergeConflict objects', async () => { const workingDirPath = await cloneRepository('merge-conflict') const repo = await buildRepository(workingDirPath) @@ -433,7 +480,8 @@ describe('Repository', function () { // expected } - let mergeConflicts = await repo.refreshMergeConflicts() + await repo.refresh() + let mergeConflicts = await repo.getMergeConflicts() const expected = [ { path: 'added-to-both.txt', @@ -470,7 +518,8 @@ describe('Repository', function () { assertDeepPropertyVals(mergeConflicts, expected) fs.unlinkSync(path.join(workingDirPath, 'removed-on-branch.txt')) - mergeConflicts = await repo.refreshMergeConflicts() + await repo.refresh() + mergeConflicts = await repo.getMergeConflicts() expected[3].fileStatus = 'deleted' assertDeepPropertyVals(mergeConflicts, expected) @@ -485,10 +534,11 @@ describe('Repository', function () { // expected } - const mergeConflicts1 = await repo.refreshMergeConflicts() + await repo.refresh() + const mergeConflicts1 = await repo.getMergeConflicts() await repo.stageFiles(['removed-on-master.txt']) - const mergeConflicts2 = await repo.refreshMergeConflicts() + const mergeConflicts2 = await repo.getMergeConflicts() assert.equal(mergeConflicts1.length, 5) assert.equal(mergeConflicts2.length, 4) @@ -518,39 +568,40 @@ describe('Repository', function () { // expected } + await repo.refresh() const mergeConflictPaths = (await repo.getMergeConflicts()).map(c => c.getPath()) assert.deepEqual(mergeConflictPaths, ['added-to-both.txt', 'modified-on-both-ours.txt', 'modified-on-both-theirs.txt', 'removed-on-branch.txt', 'removed-on-master.txt']) - let stagedFilePatches = await repo.refreshStagedChanges() + let stagedFilePatches = await repo.getStagedChanges() assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), []) await repo.stageFiles(['added-to-both.txt']) - stagedFilePatches = await repo.refreshStagedChanges() + stagedFilePatches = await repo.getStagedChanges() assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), ['added-to-both.txt']) // choose version of the file on head fs.writeFileSync(path.join(workingDirPath, 'modified-on-both-ours.txt'), 'master modification\n', 'utf8') await repo.stageFiles(['modified-on-both-ours.txt']) - stagedFilePatches = await repo.refreshStagedChanges() + stagedFilePatches = await repo.getStagedChanges() // nothing additional to stage assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), ['added-to-both.txt']) // choose version of the file on branch fs.writeFileSync(path.join(workingDirPath, 'modified-on-both-ours.txt'), 'branch modification\n', 'utf8') await repo.stageFiles(['modified-on-both-ours.txt']) - stagedFilePatches = await repo.refreshStagedChanges() + stagedFilePatches = await repo.getStagedChanges() assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), ['added-to-both.txt', 'modified-on-both-ours.txt']) // remove file that was deleted on branch fs.unlinkSync(path.join(workingDirPath, 'removed-on-branch.txt')) await repo.stageFiles(['removed-on-branch.txt']) - stagedFilePatches = await repo.refreshStagedChanges() + stagedFilePatches = await repo.getStagedChanges() assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), ['added-to-both.txt', 'modified-on-both-ours.txt', 'removed-on-branch.txt']) // remove file that was deleted on master fs.unlinkSync(path.join(workingDirPath, 'removed-on-master.txt')) await repo.stageFiles(['removed-on-master.txt']) - stagedFilePatches = await repo.refreshStagedChanges() + stagedFilePatches = await repo.getStagedChanges() // nothing additional to stage assert.deepEqual(stagedFilePatches.map(patch => patch.getPath()), ['added-to-both.txt', 'modified-on-both-ours.txt', 'removed-on-branch.txt']) }) @@ -627,8 +678,8 @@ describe('Repository', function () { await repo.abortMerge() assert.equal(await repo.isMerging(), false) - assert.equal((await repo.refreshStagedChanges()).length, 0) - assert.equal((await repo.refreshUnstagedChanges()).length, 1) + assert.equal((await repo.getStagedChanges()).length, 0) + assert.equal((await repo.getUnstagedChanges()).length, 1) assert.equal(fs.readFileSync(path.join(workingDirPath, 'fruit.txt')), 'a change\n') }) }) @@ -644,8 +695,8 @@ describe('Repository', function () { } fs.writeFileSync(path.join(workingDirPath, 'animal.txt'), 'a change\n') - const stagedChanges = await repo.refreshStagedChanges() - const unstagedChanges = await repo.refreshUnstagedChanges() + const stagedChanges = await repo.getStagedChanges() + const unstagedChanges = await repo.getUnstagedChanges() assert.equal(await repo.isMerging(), true) try { @@ -655,8 +706,8 @@ describe('Repository', function () { assert.match(e.command, /^git merge --abort/) } assert.equal(await repo.isMerging(), true) - assert.deepEqual(await repo.refreshStagedChanges(), stagedChanges) - assert.deepEqual(await repo.refreshUnstagedChanges(), unstagedChanges) + assert.deepEqual(await repo.getStagedChanges(), stagedChanges) + assert.deepEqual(await repo.getUnstagedChanges(), unstagedChanges) }) }) }) diff --git a/test/models/workspace-change-observer.test.js b/test/models/workspace-change-observer.test.js index f3111ac395..92d4fc9450 100644 --- a/test/models/workspace-change-observer.test.js +++ b/test/models/workspace-change-observer.test.js @@ -70,7 +70,8 @@ describe('WorkspaceChangeObserver', () => { assert.isTrue(changeSpy.calledOnce) changeSpy.reset() - const [unstagedChange] = await repository1.refreshUnstagedChanges() + repository1.refresh() + const [unstagedChange] = await repository1.getUnstagedChanges() await repository1.applyPatchToIndex(unstagedChange) await changeObserver.lastIndexChangePromise assert.isTrue(changeSpy.called) diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index 2a7e579f5e..0808287e93 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -216,36 +216,40 @@ describe('CommitView', () => { const view = new CommitView({workspace, commandRegistry, stagedChangesExist: false, lastCommit: {message: 'previous commit\'s message'}}) const {editor, amend} = view.refs - // choose to keep current commit message when prompted - confirmChoice = 1 editor.setText('some commit message') - await view.update({repository, stagedChangesExist: true}) + + // When checking amend with a commit message typed, prompt for whether + // to keep the current message or replace it with the last commit's message. + confirmChoice = 0 assert.isFalse(amend.checked) + assert.equal(editor.getText(), 'some commit message') + amend.click() assert.isTrue(amend.checked) + assert.equal(atom.confirm.callCount, 1) assert.equal(editor.getText(), 'some commit message') - editor.setText('some commit message') - + // When unchecking amend with a different commit message typed, keep the + // current message without prompting. amend.click() assert.isFalse(amend.checked) + assert.equal(atom.confirm.callCount, 1) assert.equal(editor.getText(), 'some commit message') - // choose to discard current commit message when prompted - confirmChoice = 0 - editor.setText('some commit message') - - await view.update({repository, stagedChangesExist: true}) - assert.isFalse(amend.checked) + // When checking amend and choosing to discard the current message, replace + // the message with the last commit's message. + confirmChoice = 1 amend.click() assert.isTrue(amend.checked) + assert.equal(atom.confirm.callCount, 2) assert.equal(editor.getText(), 'previous commit\'s message') - editor.setText('some commit message') - + // When unchecking amend with the previous commit's message typed, clear + // the message box without prompting. amend.click() assert.isFalse(amend.checked) + assert.equal(atom.confirm.callCount, 2) assert.equal(editor.getText(), '') }) @@ -261,6 +265,19 @@ describe('CommitView', () => { await view.commit() assert.isFalse(amend.checked) }) - }) + it('calls props.setAmending() when the box is checked or unchecked', async function () { + const setAmending = sinon.spy() + const workdirPath = await cloneRepository('three-files') + const repository = await buildRepository(workdirPath) + const view = new CommitView({workspace, commandRegistry, stagedChangesExist: false, lastCommit: {message: 'previous commit\'s message'}, setAmending}) + const {editor, amend} = view.refs + + amend.click() + assert.deepEqual(setAmending.args, [[true]]) + + amend.click() + assert.deepEqual(setAmending.args, [[true], [false]]) + }) + }) })