Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add merging functionality and UI #600

Merged
merged 8 commits into from
Oct 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions jupyterlab_git/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,17 @@ async def checkout_all(self, path):
return {"code": code, "command": " ".join(cmd), "message": error}
return {"code": code}

async def merge(self, branch, path):
"""
Execute git merge command & return the result.
"""
cmd = ["git", "merge", branch]
code, output, error = await execute(cmd, cwd=path)

if code != 0:
return {"code": code, "command": " ".join(cmd), "message": error}
return {"code": code, "message": output.strip()}

async def commit(self, commit_msg, amend, path):
"""
Execute git commit <filename> command & return the result.
Expand Down
20 changes: 20 additions & 0 deletions jupyterlab_git/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,25 @@ async def post(self, path: str = ""):
self.finish(json.dumps(body))


class GitMergeHandler(GitHandler):
"""
Handler for git merge '<merge_from> <merge_into>'. Merges into current working branch
"""

@tornado.web.authenticated
async def post(self, path: str = ""):
"""
POST request handler, merges branches
"""
data = self.get_json_body()
branch = data["branch"]
body = await self.git.merge(branch, self.url2localpath(path))

if body["code"] != 0:
self.set_status(500)
self.finish(json.dumps(body))


class GitCommitHandler(GitHandler):
"""
Handler for 'git commit -m <message>' and 'git commit --amend'. Commits files.
Expand Down Expand Up @@ -831,6 +850,7 @@ def setup_handlers(web_app):
("/diff", GitDiffHandler),
("/init", GitInitHandler),
("/log", GitLogHandler),
("/merge", GitMergeHandler),
("/pull", GitPullHandler),
("/push", GitPushHandler),
("/remote/add", GitRemoteAddHandler),
Expand Down
3 changes: 3 additions & 0 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
{
"command": "git:clone"
},
{
"command": "git:merge"
},
{
"command": "git:push"
},
Expand Down
87 changes: 82 additions & 5 deletions src/commandsAndMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Dialog,
InputDialog,
MainAreaWidget,
ReactWidget,
showDialog,
showErrorMessage,
Toolbar,
Expand All @@ -19,11 +20,12 @@ import { ArrayExt, toArray } from '@lumino/algorithm';
import { CommandRegistry } from '@lumino/commands';
import { PromiseDelegate } from '@lumino/coreutils';
import { Message } from '@lumino/messaging';
import { ContextMenu, Menu, Panel } from '@lumino/widgets';
import { ContextMenu, Menu, Panel, Widget } from '@lumino/widgets';
import * as React from 'react';
import { DiffModel } from './components/diff/model';
import { createPlainTextDiff } from './components/diff/PlainTextDiff';
import { CONTEXT_COMMANDS } from './components/FileList';
import { MergeBranchDialog } from './components/MergeBranchDialog';
import { AUTH_ERROR_MESSAGES, requestAPI } from './git';
import { logger } from './logger';
import { getDiffProvider, GitExtension } from './model';
Expand Down Expand Up @@ -193,7 +195,7 @@ export function addCommands(

if (result.button.accept) {
logger.log({
message: trans.__('Initializing...'),
message: trans.__('Initializing'),
level: Level.RUNNING
});
try {
Expand Down Expand Up @@ -307,7 +309,7 @@ export function addCommands(
if (result.button.accept && result.value) {
logger.log({
level: Level.RUNNING,
message: trans.__('Cloning...')
message: trans.__('Cloning')
});
try {
const details = await Private.showGitOperationDialog<IGitCloneArgs>(
Expand Down Expand Up @@ -358,7 +360,7 @@ export function addCommands(
execute: async args => {
logger.log({
level: Level.RUNNING,
message: trans.__('Pushing...')
message: trans.__('Pushing')
});
try {
const details = await Private.showGitOperationDialog(
Expand Down Expand Up @@ -393,7 +395,7 @@ export function addCommands(
execute: async () => {
logger.log({
level: Level.RUNNING,
message: trans.__('Pulling...')
message: trans.__('Pulling')
});
try {
const details = await Private.showGitOperationDialog(
Expand Down Expand Up @@ -574,6 +576,80 @@ export function addCommands(
icon: diffIcon.bindprops({ stylesheet: 'menuItem' })
});

commands.addCommand(CommandIDs.gitMerge, {
label: trans.__('Merge Branch…'),
caption: trans.__('Merge selected branch in the current branch'),
execute: async args => {
let { branch }: { branch?: string } = args ?? {};

if (!branch) {
// Prompts user to pick a branch
const localBranches = gitModel.branches.filter(
branch => !branch.is_current_branch && !branch.is_remote_branch
);

const widgetId = 'git-dialog-MergeBranch';
let anchor = document.querySelector<HTMLDivElement>(`#${widgetId}`);
if (!anchor) {
anchor = document.createElement('div');
anchor.id = widgetId;
document.body.appendChild(anchor);
}

const waitForDialog = new PromiseDelegate<string | null>();
const dialog = ReactWidget.create(
<MergeBranchDialog
currentBranch={gitModel.currentBranch.name}
branches={localBranches}
onClose={(branch?: string) => {
dialog.dispose();
waitForDialog.resolve(branch ?? null);
}}
trans={trans}
/>
);

Widget.attach(dialog, anchor);

branch = await waitForDialog.promise;
}

if (branch) {
logger.log({
level: Level.RUNNING,
message: trans.__("Merging branch '%1'…", branch)
});
try {
await gitModel.merge(branch);
} catch (err) {
logger.log({
level: Level.ERROR,
message: trans.__(
"Failed to merge branch '%1' into '%2'.",
branch,
gitModel.currentBranch.name
),
error: err as Error
});
return;
}

logger.log({
level: Level.SUCCESS,
message: trans.__(
"Branch '%1' merged into '%2'.",
branch,
gitModel.currentBranch.name
)
});
}
},
isEnabled: () =>
gitModel.branches.some(
branch => !branch.is_current_branch && !branch.is_remote_branch
)
});

/* Context menu commands */
commands.addCommand(ContextCommandIDs.gitFileOpen, {
label: trans.__('Open'),
Expand Down Expand Up @@ -1073,6 +1149,7 @@ export function createGitMenu(
[
CommandIDs.gitInit,
CommandIDs.gitClone,
CommandIDs.gitMerge,
CommandIDs.gitPush,
CommandIDs.gitPull,
CommandIDs.gitAddRemote,
Expand Down
58 changes: 45 additions & 13 deletions src/components/BranchMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Dialog, showDialog, showErrorMessage } from '@jupyterlab/apputils';
import { TranslationBundle } from '@jupyterlab/translation';
import { CommandRegistry } from '@lumino/commands';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ClearIcon from '@material-ui/icons/Clear';
Expand All @@ -20,8 +21,8 @@ import {
newBranchButtonClass,
wrapperClass
} from '../style/BranchMenu';
import { branchIcon, trashIcon } from '../style/icons';
import { Git, IGitExtension, Level } from '../tokens';
import { branchIcon, mergeIcon, trashIcon } from '../style/icons';
import { CommandIDs, Git, IGitExtension, Level } from '../tokens';
import { ActionButton } from './ActionButton';
import { NewBranchDialog } from './NewBranchDialog';

Expand Down Expand Up @@ -107,6 +108,11 @@ export interface IBranchMenuProps {
*/
branching: boolean;

/**
* Jupyter App commands registry
*/
commands: CommandRegistry;

/**
* Extension logger
*/
Expand Down Expand Up @@ -258,7 +264,11 @@ export class BranchMenu extends React.Component<
return (
<ListItem
button
title={this.props.trans.__('Switch to branch: %1', branch.name)}
title={
!isActive
? this.props.trans.__('Switch to branch: %1', branch.name)
: ''
}
className={classes(
listItemClass,
isActive ? activeListItemClass : null
Expand All @@ -269,15 +279,28 @@ export class BranchMenu extends React.Component<
<branchIcon.react className={listItemIconClass} tag="span" />
<span className={nameClass}>{branch.name}</span>
{!branch.is_remote_branch && !isActive && (
<ActionButton
className={hiddenButtonStyle}
icon={trashIcon}
title={this.props.trans.__('Delete this branch locally')}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
this._onDeleteBranch(branch.name);
}}
/>
<>
<ActionButton
className={hiddenButtonStyle}
icon={trashIcon}
title={this.props.trans.__('Delete this branch locally')}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
this._onDeleteBranch(branch.name);
}}
/>
<ActionButton
className={hiddenButtonStyle}
icon={mergeIcon}
title={this.props.trans.__(
'Merge this branch into the current one'
)}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
this._onMergeBranch(branch.name);
}}
/>
</>
)}
</ListItem>
);
Expand Down Expand Up @@ -355,6 +378,15 @@ export class BranchMenu extends React.Component<
}
};

/**
* Callback on merge branch name button
*
* @param branchName Branch name
*/
private _onMergeBranch = async (branch: string): Promise<void> => {
await this.props.commands.execute(CommandIDs.gitMerge, { branch });
};

/**
* Callback invoked upon clicking a button to create a new branch.
*
Expand Down Expand Up @@ -413,7 +445,7 @@ export class BranchMenu extends React.Component<

self.props.logger.log({
level: Level.RUNNING,
message: self.props.trans.__('Switching branch...')
message: self.props.trans.__('Switching branch')
});

try {
Expand Down
Loading