From 9f122e8420c6e423783fd623a9a613a64455b5c7 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Fri, 26 Feb 2021 00:22:36 -0800 Subject: [PATCH 1/5] Add useCommitAndPush to settings & GitPanel props --- schema/plugin.json | 6 ++++++ src/components/CommitBox.tsx | 9 ++++++++- src/components/GitPanel.tsx | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/schema/plugin.json b/schema/plugin.json index 2f8062414..66bffca1a 100644 --- a/schema/plugin.json +++ b/schema/plugin.json @@ -58,6 +58,12 @@ "title": "Simple staging flag", "description": "If true, use a simplified concept of staging. Only files with changes are shown (instead of showing staged/changed/untracked), and all files with changes will be automatically staged", "default": false + }, + "useCommitAndPush": { + "type": "boolean", + "title": "Combine commit and push in one button", + "description": "If true, combine commit and push in one button.", + "default": false } }, "jupyter.lab.shortcuts": [ diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 382d73fe6..3641c74ed 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -19,6 +19,11 @@ export interface ICommitBoxProps { */ commands: CommandRegistry; + /** + * Boolean indicating whether to use simplified commit-and-push instead of commit button + */ + useCommitAndPush: boolean; + /** * Boolean indicating whether files currently exist which have changes to commit. */ @@ -91,7 +96,9 @@ export class CommitBox extends React.Component< ? this.props.trans.__('Disabled: No files are staged for commit') : !this.state.summary ? this.props.trans.__('Disabled: No commit message summary') - : this.props.trans.__('Commit'); + : !this.props.useCommitAndPush + ? this.props.trans.__('Commit') + : this.props.trans.__('Commit and push'); const shortcutHint = CommandRegistry.formatKeystroke( this._getSubmitKeystroke() diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index e929c4a1d..ed535e256 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -380,6 +380,7 @@ export class GitPanel extends React.Component { 0} trans={this.props.trans} + useCommitAndPush={this.props.settings.composite['useCommitAndPush'] as boolean} onCommit={this.commitMarkedFiles} commands={this.props.commands} /> @@ -387,6 +388,7 @@ export class GitPanel extends React.Component { From da4af48e3476ae9041d37a6d0489e940dc20961a Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Fri, 26 Feb 2021 21:10:46 -0800 Subject: [PATCH 2/5] Enable 'commit and push' button label --- src/components/CommitBox.tsx | 17 +++++++++++++++-- src/components/GitPanel.tsx | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 3641c74ed..59909a077 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -9,6 +9,7 @@ import { import { TranslationBundle } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { CommandIDs } from '../tokens'; +import { Git } from '../tokens'; /** * Interface describing component properties. @@ -19,6 +20,11 @@ export interface ICommitBoxProps { */ commands: CommandRegistry; + /** + * Current list of branches. + */ + branches: Git.IBranch[]; + /** * Boolean indicating whether to use simplified commit-and-push instead of commit button */ @@ -98,6 +104,8 @@ export class CommitBox extends React.Component< ? this.props.trans.__('Disabled: No commit message summary') : !this.props.useCommitAndPush ? this.props.trans.__('Commit') + : !this.props.branches.some(branch => branch.is_remote_branch) + ? this.props.trans.__('Disabled: No remote repository defined') : this.props.trans.__('Commit and push'); const shortcutHint = CommandRegistry.formatKeystroke( @@ -132,7 +140,9 @@ export class CommitBox extends React.Component< className={commitButtonClass} type="button" title={title} - value={this.props.trans.__('Commit')} + value={!this.props.useCommitAndPush + ? this.props.trans.__('Commit') + : this.props.trans.__('Commit and push')} disabled={disabled} onClick={this._onCommitSubmit} /> @@ -144,7 +154,10 @@ export class CommitBox extends React.Component< * Whether a commit can be performed (files are staged and summary is not empty). */ private _canCommit(): boolean { - return !!(this.props.hasFiles && this.state.summary); + const canAlsoPush = this.props.useCommitAndPush + ? this.props.branches.some(branch => branch.is_remote_branch) + : true; + return !!(this.props.hasFiles && this.state.summary && canAlsoPush); } /** diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index ed535e256..48c114e2f 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -383,6 +383,7 @@ export class GitPanel extends React.Component { useCommitAndPush={this.props.settings.composite['useCommitAndPush'] as boolean} onCommit={this.commitMarkedFiles} commands={this.props.commands} + branches={this.state.branches} /> ) : ( { useCommitAndPush={this.props.settings.composite['useCommitAndPush'] as boolean} onCommit={this.commitStagedFiles} commands={this.props.commands} + branches={this.state.branches} /> )} From d3aaefc55b059799aa7a1d0b71c9990b108c42b2 Mon Sep 17 00:00:00 2001 From: Konstantin Taletskiy Date: Sat, 27 Feb 2021 14:45:04 -0800 Subject: [PATCH 3/5] Add push action at the end of commit --- src/components/GitPanel.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 48c114e2f..1eda8b511 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -260,6 +260,10 @@ export class GitPanel extends React.Component { console.error(error); this.props.logger.log({ ...errorLog, error }); } + // If enabled commit and push, push here + if (this.props.settings.composite['useCommitAndPush']) { + await this.props.commands.execute(CommandIDs.gitPush); + } }; /** From 3aa6b484421e36848e93db98ae6353f845341806 Mon Sep 17 00:00:00 2001 From: Frederic COLLONVAL Date: Sat, 3 Apr 2021 12:28:02 +0200 Subject: [PATCH 4/5] Finalize commit and push --- schema/plugin.json | 8 +- src/components/CommitBox.tsx | 39 ++++------ src/components/GitPanel.tsx | 25 ++++-- tests/test-components/CommitBox.spec.tsx | 66 ++++++++++------ tests/test-components/GitPanel.spec.tsx | 99 ++++++++++++++++++++++-- 5 files changed, 169 insertions(+), 68 deletions(-) diff --git a/schema/plugin.json b/schema/plugin.json index 66bffca1a..7b5848021 100644 --- a/schema/plugin.json +++ b/schema/plugin.json @@ -59,11 +59,11 @@ "description": "If true, use a simplified concept of staging. Only files with changes are shown (instead of showing staged/changed/untracked), and all files with changes will be automatically staged", "default": false }, - "useCommitAndPush": { + "commitAndPush": { "type": "boolean", - "title": "Combine commit and push in one button", - "description": "If true, combine commit and push in one button.", - "default": false + "title": "Trigger push on commit", + "description": "Whether to trigger or not a push for each commit.", + "default": true } }, "jupyter.lab.shortcuts": [ diff --git a/src/components/CommitBox.tsx b/src/components/CommitBox.tsx index 59909a077..f8f4c7d51 100644 --- a/src/components/CommitBox.tsx +++ b/src/components/CommitBox.tsx @@ -1,15 +1,14 @@ +import { TranslationBundle } from '@jupyterlab/translation'; +import { CommandRegistry } from '@lumino/commands'; import * as React from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { - commitFormClass, - commitSummaryClass, + commitButtonClass, commitDescriptionClass, - commitButtonClass + commitFormClass, + commitSummaryClass } from '../style/CommitBox'; -import { TranslationBundle } from '@jupyterlab/translation'; -import { CommandRegistry } from '@lumino/commands'; import { CommandIDs } from '../tokens'; -import { Git } from '../tokens'; /** * Interface describing component properties. @@ -21,23 +20,20 @@ export interface ICommitBoxProps { commands: CommandRegistry; /** - * Current list of branches. + * Boolean indicating whether files currently exist which have changes to commit. */ - branches: Git.IBranch[]; + hasFiles: boolean; /** - * Boolean indicating whether to use simplified commit-and-push instead of commit button + * Commit button label */ - useCommitAndPush: boolean; + label: string; - /** - * Boolean indicating whether files currently exist which have changes to commit. - */ - hasFiles: boolean; /** * The application language translator. */ trans: TranslationBundle; + /** * Callback to invoke in order to commit changes. * @@ -102,11 +98,7 @@ export class CommitBox extends React.Component< ? this.props.trans.__('Disabled: No files are staged for commit') : !this.state.summary ? this.props.trans.__('Disabled: No commit message summary') - : !this.props.useCommitAndPush - ? this.props.trans.__('Commit') - : !this.props.branches.some(branch => branch.is_remote_branch) - ? this.props.trans.__('Disabled: No remote repository defined') - : this.props.trans.__('Commit and push'); + : this.props.label; const shortcutHint = CommandRegistry.formatKeystroke( this._getSubmitKeystroke() @@ -140,9 +132,7 @@ export class CommitBox extends React.Component< className={commitButtonClass} type="button" title={title} - value={!this.props.useCommitAndPush - ? this.props.trans.__('Commit') - : this.props.trans.__('Commit and push')} + value={this.props.label} disabled={disabled} onClick={this._onCommitSubmit} /> @@ -154,10 +144,7 @@ export class CommitBox extends React.Component< * Whether a commit can be performed (files are staged and summary is not empty). */ private _canCommit(): boolean { - const canAlsoPush = this.props.useCommitAndPush - ? this.props.branches.some(branch => branch.is_remote_branch) - : true; - return !!(this.props.hasFiles && this.state.summary && canAlsoPush); + return !!(this.props.hasFiles && this.state.summary); } /** diff --git a/src/components/GitPanel.tsx b/src/components/GitPanel.tsx index 1eda8b511..cf769c67c 100644 --- a/src/components/GitPanel.tsx +++ b/src/components/GitPanel.tsx @@ -2,12 +2,12 @@ import { showDialog } from '@jupyterlab/apputils'; import { PathExt } from '@jupyterlab/coreutils'; import { FileBrowserModel } from '@jupyterlab/filebrowser'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; +import { TranslationBundle } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { JSONObject } from '@lumino/coreutils'; import { Signal } from '@lumino/signaling'; import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; -import { TranslationBundle } from '@jupyterlab/translation'; import * as React from 'react'; import { Logger } from '../logger'; import { GitExtension } from '../model'; @@ -260,8 +260,11 @@ export class GitPanel extends React.Component { console.error(error); this.props.logger.log({ ...errorLog, error }); } + const hasRemote = this.props.model.branches.some( + branch => branch.is_remote_branch + ); // If enabled commit and push, push here - if (this.props.settings.composite['useCommitAndPush']) { + if (this.props.settings.composite['commitAndPush'] && hasRemote) { await this.props.commands.execute(CommandIDs.gitPush); } }; @@ -371,6 +374,14 @@ export class GitPanel extends React.Component { * @returns React element */ private _renderChanges(): React.ReactElement { + const hasRemote = this.props.model.branches.some( + branch => branch.is_remote_branch + ); + const commitAndPush = + (this.props.settings.composite['commitAndPush'] as boolean) && hasRemote; + const buttonLabel = commitAndPush + ? this.props.trans.__('Commit and Push') + : this.props.trans.__('Commit'); return ( { /> {this.props.settings.composite['simpleStaging'] ? ( 0} trans={this.props.trans} - useCommitAndPush={this.props.settings.composite['useCommitAndPush'] as boolean} + label={buttonLabel} onCommit={this.commitMarkedFiles} - commands={this.props.commands} - branches={this.state.branches} /> ) : ( )} diff --git a/tests/test-components/CommitBox.spec.tsx b/tests/test-components/CommitBox.spec.tsx index b1cce5a0d..313e605a7 100644 --- a/tests/test-components/CommitBox.spec.tsx +++ b/tests/test-components/CommitBox.spec.tsx @@ -1,19 +1,18 @@ -import * as React from 'react'; -import 'jest'; -import { shallow } from 'enzyme'; -import { CommitBox} from '../../src/components/CommitBox'; -import { CommandRegistry } from '@lumino/commands'; import { nullTranslator } from '@jupyterlab/translation'; +import { CommandRegistry } from '@lumino/commands'; +import { shallow } from 'enzyme'; +import 'jest'; +import * as React from 'react'; +import { CommitBox } from '../../src/components/CommitBox'; import { CommandIDs } from '../../src/tokens'; describe('CommitBox', () => { - - const defaultCommands = new CommandRegistry() + const defaultCommands = new CommandRegistry(); defaultCommands.addKeyBinding({ keys: ['Accel Enter'], command: CommandIDs.gitSubmitCommand, selector: '.jp-git-CommitBox' - }) + }); const trans = nullTranslator.load('jupyterlab-git'); @@ -23,7 +22,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }); expect(box).toBeInstanceOf(CommitBox); }); @@ -33,7 +33,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }); expect(box.state.summary).toEqual(''); }); @@ -43,7 +44,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }); expect(box.state.description).toEqual(''); }); @@ -55,29 +57,35 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="text"]').first(); - expect(node.prop('placeholder')).toEqual('Summary (Ctrl+Enter to commit)'); + expect(node.prop('placeholder')).toEqual( + 'Summary (Ctrl+Enter to commit)' + ); }); it('should adjust placeholder text for the commit message summary when keybinding changes', () => { - const adjustedCommands = new CommandRegistry() + const adjustedCommands = new CommandRegistry(); adjustedCommands.addKeyBinding({ keys: ['Shift Enter'], command: CommandIDs.gitSubmitCommand, selector: '.jp-git-CommitBox' - }) + }); const props = { onCommit: async () => {}, hasFiles: false, commands: adjustedCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="text"]').first(); - expect(node.prop('placeholder')).toEqual('Summary (Shift+Enter to commit)'); + expect(node.prop('placeholder')).toEqual( + 'Summary (Shift+Enter to commit)' + ); }); it('should set a `title` attribute on the input element to provide a commit message summary', () => { @@ -85,7 +93,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="text"]').first(); @@ -97,7 +106,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('TextareaAutosize').first(); @@ -109,7 +119,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('TextareaAutosize').first(); @@ -121,7 +132,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="button"]').first(); @@ -133,7 +145,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="button"]').first(); @@ -145,7 +158,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: false, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="button"]').first(); @@ -158,7 +172,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: true, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); const node = component.find('input[type="button"]').first(); @@ -171,7 +186,8 @@ describe('CommitBox', () => { onCommit: async () => {}, hasFiles: true, commands: defaultCommands, - trans: trans + trans: trans, + label: 'Commit' }; const component = shallow(); component.setState({ diff --git a/tests/test-components/GitPanel.spec.tsx b/tests/test-components/GitPanel.spec.tsx index 5d1732eff..eea594829 100644 --- a/tests/test-components/GitPanel.spec.tsx +++ b/tests/test-components/GitPanel.spec.tsx @@ -1,7 +1,10 @@ import * as apputils from '@jupyterlab/apputils'; import { nullTranslator } from '@jupyterlab/translation'; import { JSONObject } from '@lumino/coreutils'; +import { shallow } from 'enzyme'; import 'jest'; +import React from 'react'; +import { CommitBox } from '../../src/components/CommitBox'; import { GitPanel, IGitPanelProps } from '../../src/components/GitPanel'; import * as git from '../../src/git'; import { Logger } from '../../src/logger'; @@ -34,13 +37,15 @@ const mockedResponses: IMockedResponses = { * @private * @returns mock settings */ -function MockSettings() { +function MockSettings(commitAndPush = true) { return { changed: { connect: () => true, disconnect: () => true }, - composite: {} + composite: { + commitAndPush + } }; } @@ -52,7 +57,9 @@ describe('GitPanel', () => { commands: null, logger: new Logger(), settings: null, - filebrowser: null, + filebrowser: { + path: '/dummy/path' + } as any, trans: trans }; @@ -65,8 +72,7 @@ describe('GitPanel', () => { props.model = new GitModel('/server/root'); props.model.pathRepository = '/path/to/repo'; - // @ts-ignore - props.settings = MockSettings(); + props.settings = MockSettings() as any; await props.model.ready; }); @@ -235,4 +241,87 @@ describe('GitPanel', () => { expect(spy).not.toHaveBeenCalled(); }); }); + + describe('#render()', () => { + beforeEach(() => { + props.commands = { + keyBindings: { find: jest.fn() } + } as any; + props.model = { + branches: [], + headChanged: { + connect: jest.fn() + }, + markChanged: { + connect: jest.fn() + }, + repositoryChanged: { + connect: jest.fn() + }, + statusChanged: { + connect: jest.fn() + } + } as any; + + props.settings = MockSettings() as any; + }); + + it('should render Commit and Push if there is a remote branch', () => { + (props.model as any).branches = [ + { + is_remote_branch: true, + is_current_branch: false, + name: 'remote', + tag: null, + top_commit: 'hash', + upstream: 'origin' + } + ]; + + const panel = shallow(); + panel.setState({ + repository: '/path' + }); + expect(panel.find(CommitBox).prop('label')).toEqual('Commit and Push'); + }); + + it('should render Commit if there is no remote branch', () => { + (props.model as any).branches = [ + { + is_remote_branch: false, + is_current_branch: false, + name: 'local', + tag: null, + top_commit: 'hash', + upstream: null + } + ]; + + const panel = shallow(); + panel.setState({ + repository: '/path' + }); + expect(panel.find(CommitBox).prop('label')).toEqual('Commit'); + }); + + it('should render Commit if there is a remote branch but commitAndPush is false', () => { + (props.model as any).branches = [ + { + is_remote_branch: true, + is_current_branch: false, + name: 'remote', + tag: null, + top_commit: 'hash', + upstream: 'origin' + } + ]; + props.settings = MockSettings(false) as any; + + const panel = shallow(); + panel.setState({ + repository: '/path' + }); + expect(panel.find(CommitBox).prop('label')).toEqual('Commit'); + }); + }); }); From 4444de0ee37e26d4a7849812f845370b6c7da994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Sat, 3 Apr 2021 13:11:12 +0200 Subject: [PATCH 5/5] Add new setting to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc3e8f219..1d45aafe1 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Once installed, extension behavior can be modified via the following settings wh - **blockWhileCommandExecutes**: suspend JupyterLab user interaction until Git commands (e.g., `commit`, `pull`, `reset`, `revert`) finish executing. Setting this to `true` helps mitigate potential race conditions leading to data loss, conflicts, and a broken Git history. Unless running a slow network, UI suspension should not interfere with standard workflows. Setting this to `false` allows for actions to trigger multiple concurrent Git actions. - **cancelPullMergeConflict**: cancel pulling changes from a remote repository if there exists a merge conflict. If set to `true`, when fetching and integrating changes from a remote repository, a conflicting merge is canceled and the working tree left untouched. +- **commitAndPush**: Whether to trigger or not a push for each commit; default is `true`. - **disableBranchWithChanges**: disable all branch operations, such as creating a new branch or switching to a different branch, when there are changed/staged files. When set to `true`, this setting guards against overwriting and/or losing uncommitted changes. - **displayStatus**: display Git extension status updates in the JupyterLab status bar. If `true`, the extension displays status updates in the JupyterLab status bar, such as when pulling and pushing changes, switching branches, and polling for changes. Depending on the level of extension activity, some users may find the status updates distracting. In which case, setting this to `false` should reduce visual noise. - **doubleClickDiff**: double click a file in the Git extension panel to open a diff of the file instead of opening the file for editing.