From 261c15e3100f161c96ac36bfb05b2ed22a84547c Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Thu, 7 Jul 2022 16:11:23 -0700
Subject: [PATCH 01/29] implement name-url pair for adding remote repo
---
src/commandsAndMenu.tsx | 18 ++++++---
src/tokens.ts | 8 ++++
src/widgets/GitAddRemoteForm.tsx | 69 ++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+), 6 deletions(-)
create mode 100644 src/widgets/GitAddRemoteForm.tsx
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index bddbc9986..40c66db7e 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -1,7 +1,6 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import {
Dialog,
- InputDialog,
MainAreaWidget,
ReactWidget,
showDialog,
@@ -47,6 +46,7 @@ import {
} from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
+import { GitAddRemoteForm } from './widgets/GitAddRemoteForm';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
export interface IGitCloneArgs {
@@ -265,16 +265,22 @@ export function addCommands(
return;
}
let url = args['url'] as string;
- const name = args['name'] as string;
+ let name = args['name'] as string;
if (!url) {
- const result = await InputDialog.getText({
+ const remoteRepo = await showDialog({
title: trans.__('Add a remote repository'),
- placeholder: trans.__('Remote Git repository URL')
+ body: new GitAddRemoteForm(
+ trans,
+ 'Enter remote repository name and url',
+ '',
+ gitModel
+ )
});
- if (result.button.accept) {
- url = result.value;
+ if (remoteRepo.button.accept) {
+ name = remoteRepo.value.name;
+ url = remoteRepo.value.url;
}
}
diff --git a/src/tokens.ts b/src/tokens.ts
index 62128cba6..8509aee62 100644
--- a/src/tokens.ts
+++ b/src/tokens.ts
@@ -987,6 +987,14 @@ export namespace Git {
cache_credentials?: boolean;
}
+ /**
+ * Structure for the request to the Git Remote Add API.
+ */
+ export interface IGitRemote {
+ url: string;
+ name: string;
+ }
+
/**
* Structure for the request to the Git Clone API.
*/
diff --git a/src/widgets/GitAddRemoteForm.tsx b/src/widgets/GitAddRemoteForm.tsx
new file mode 100644
index 000000000..fca19d3e4
--- /dev/null
+++ b/src/widgets/GitAddRemoteForm.tsx
@@ -0,0 +1,69 @@
+import { Dialog } from '@jupyterlab/apputils';
+import { TranslationBundle } from '@jupyterlab/translation';
+import { Widget } from '@lumino/widgets';
+import { Git } from '../tokens';
+import { GitExtension } from '../model';
+
+/**
+ * The UI for the add remote repository form
+ */
+export class GitAddRemoteForm
+ extends Widget
+ implements Dialog.IBodyWidget
+{
+ constructor(
+ trans: TranslationBundle,
+ textContent = trans.__('Enter remote repository name and url'),
+ warningContent = '',
+ model: GitExtension
+ ) {
+ super();
+ this._trans = trans;
+ this._model = model;
+ this.node.appendChild(this.createBody(textContent, warningContent));
+ }
+
+ private createBody(textContent: string, warningContent: string): HTMLElement {
+ console.log(this._model);
+ const node = document.createElement('div');
+ node.className = 'jp-AddRemoteBox';
+
+ const label = document.createElement('label');
+
+ const text = document.createElement('span');
+ text.textContent = textContent;
+ this._name = document.createElement('input');
+ this._name.type = 'text';
+ this._name.placeholder = this._trans.__('namename');
+ this._url = document.createElement('input');
+ this._url.type = 'text';
+ this._url.placeholder = this._trans.__('Remote GIt repository URL');
+
+ label.appendChild(text);
+ label.appendChild(this._name);
+ label.appendChild(this._url);
+
+ const warning = document.createElement('div');
+ warning.className = 'jp-AddRemoteBox-warning';
+ warning.textContent = warningContent;
+
+ node.appendChild(label);
+ node.appendChild(warning);
+
+ return node;
+ }
+
+ /**
+ * Returns the input value.
+ */
+ getValue(): Git.IGitRemote {
+ return {
+ url: this._url.value,
+ name: this._name.value
+ };
+ }
+ protected _trans: TranslationBundle;
+ private _model: GitExtension;
+ private _url: HTMLInputElement;
+ private _name: HTMLInputElement;
+}
From c1e6e90b032fec445baea7658a1d8041db5f11d9 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Thu, 7 Jul 2022 16:11:23 -0700
Subject: [PATCH 02/29] implement name-url pair for adding remote repo
---
src/commandsAndMenu.tsx | 18 ++++++---
src/tokens.ts | 8 ++++
src/widgets/GitAddRemoteForm.tsx | 69 ++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+), 6 deletions(-)
create mode 100644 src/widgets/GitAddRemoteForm.tsx
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index 2bb5f8887..9b22a7659 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -1,7 +1,6 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import {
Dialog,
- InputDialog,
MainAreaWidget,
ReactWidget,
showDialog,
@@ -47,6 +46,7 @@ import {
} from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
+import { GitAddRemoteForm } from './widgets/GitAddRemoteForm';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
export interface IGitCloneArgs {
@@ -265,16 +265,22 @@ export function addCommands(
return;
}
let url = args['url'] as string;
- const name = args['name'] as string;
+ let name = args['name'] as string;
if (!url) {
- const result = await InputDialog.getText({
+ const remoteRepo = await showDialog({
title: trans.__('Add a remote repository'),
- placeholder: trans.__('Remote Git repository URL')
+ body: new GitAddRemoteForm(
+ trans,
+ 'Enter remote repository name and url',
+ '',
+ gitModel
+ )
});
- if (result.button.accept) {
- url = result.value;
+ if (remoteRepo.button.accept) {
+ name = remoteRepo.value.name;
+ url = remoteRepo.value.url;
}
}
diff --git a/src/tokens.ts b/src/tokens.ts
index df1d92545..c654bb3e6 100644
--- a/src/tokens.ts
+++ b/src/tokens.ts
@@ -991,6 +991,14 @@ export namespace Git {
cache_credentials?: boolean;
}
+ /**
+ * Structure for the request to the Git Remote Add API.
+ */
+ export interface IGitRemote {
+ url: string;
+ name: string;
+ }
+
/**
* Structure for the request to the Git Clone API.
*/
diff --git a/src/widgets/GitAddRemoteForm.tsx b/src/widgets/GitAddRemoteForm.tsx
new file mode 100644
index 000000000..fca19d3e4
--- /dev/null
+++ b/src/widgets/GitAddRemoteForm.tsx
@@ -0,0 +1,69 @@
+import { Dialog } from '@jupyterlab/apputils';
+import { TranslationBundle } from '@jupyterlab/translation';
+import { Widget } from '@lumino/widgets';
+import { Git } from '../tokens';
+import { GitExtension } from '../model';
+
+/**
+ * The UI for the add remote repository form
+ */
+export class GitAddRemoteForm
+ extends Widget
+ implements Dialog.IBodyWidget
+{
+ constructor(
+ trans: TranslationBundle,
+ textContent = trans.__('Enter remote repository name and url'),
+ warningContent = '',
+ model: GitExtension
+ ) {
+ super();
+ this._trans = trans;
+ this._model = model;
+ this.node.appendChild(this.createBody(textContent, warningContent));
+ }
+
+ private createBody(textContent: string, warningContent: string): HTMLElement {
+ console.log(this._model);
+ const node = document.createElement('div');
+ node.className = 'jp-AddRemoteBox';
+
+ const label = document.createElement('label');
+
+ const text = document.createElement('span');
+ text.textContent = textContent;
+ this._name = document.createElement('input');
+ this._name.type = 'text';
+ this._name.placeholder = this._trans.__('namename');
+ this._url = document.createElement('input');
+ this._url.type = 'text';
+ this._url.placeholder = this._trans.__('Remote GIt repository URL');
+
+ label.appendChild(text);
+ label.appendChild(this._name);
+ label.appendChild(this._url);
+
+ const warning = document.createElement('div');
+ warning.className = 'jp-AddRemoteBox-warning';
+ warning.textContent = warningContent;
+
+ node.appendChild(label);
+ node.appendChild(warning);
+
+ return node;
+ }
+
+ /**
+ * Returns the input value.
+ */
+ getValue(): Git.IGitRemote {
+ return {
+ url: this._url.value,
+ name: this._name.value
+ };
+ }
+ protected _trans: TranslationBundle;
+ private _model: GitExtension;
+ private _url: HTMLInputElement;
+ private _name: HTMLInputElement;
+}
From 2b0987b0255e50c514a360b9bbcd92135107c431 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Mon, 11 Jul 2022 16:42:34 -0700
Subject: [PATCH 03/29] Style Add Remote form
---
src/widgets/GitAddRemoteForm.tsx | 2 +-
style/add-remote-box.css | 10 ++++++++++
style/base.css | 1 +
3 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 style/add-remote-box.css
diff --git a/src/widgets/GitAddRemoteForm.tsx b/src/widgets/GitAddRemoteForm.tsx
index fca19d3e4..c98072180 100644
--- a/src/widgets/GitAddRemoteForm.tsx
+++ b/src/widgets/GitAddRemoteForm.tsx
@@ -34,7 +34,7 @@ export class GitAddRemoteForm
text.textContent = textContent;
this._name = document.createElement('input');
this._name.type = 'text';
- this._name.placeholder = this._trans.__('namename');
+ this._name.placeholder = this._trans.__('name');
this._url = document.createElement('input');
this._url.type = 'text';
this._url.placeholder = this._trans.__('Remote GIt repository URL');
diff --git a/style/add-remote-box.css b/style/add-remote-box.css
new file mode 100644
index 000000000..b52968689
--- /dev/null
+++ b/style/add-remote-box.css
@@ -0,0 +1,10 @@
+.jp-AddRemoteBox input {
+ display: block;
+ width: 100%;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+jp-AddRemoteBox-warning {
+ color: var(--jp-warn-color0);
+}
\ No newline at end of file
diff --git a/style/base.css b/style/base.css
index 0a1857c04..8be69086e 100644
--- a/style/base.css
+++ b/style/base.css
@@ -9,3 +9,4 @@
@import url('diff-text.css');
@import url('variables.css');
@import url('status-widget.css');
+@import url('add-remote-box.css');
From b40ba7753373679d244594c057beccc64f142805 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Mon, 11 Jul 2022 17:57:11 -0700
Subject: [PATCH 04/29] show existing remote on add remote form
---
src/commandsAndMenu.tsx | 4 +--
src/model.ts | 15 +++++++++
...GitAddRemoteForm.tsx => AddRemoteForm.tsx} | 33 +++++++++++++++++--
style/add-remote-box.css | 18 ++++++++++
4 files changed, 65 insertions(+), 5 deletions(-)
rename src/widgets/{GitAddRemoteForm.tsx => AddRemoteForm.tsx} (60%)
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index 9b22a7659..5fcd54c4d 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -46,7 +46,7 @@ import {
} from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
-import { GitAddRemoteForm } from './widgets/GitAddRemoteForm';
+import { AddRemoteForm } from './widgets/AddRemoteForm';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
export interface IGitCloneArgs {
@@ -270,7 +270,7 @@ export function addCommands(
if (!url) {
const remoteRepo = await showDialog({
title: trans.__('Add a remote repository'),
- body: new GitAddRemoteForm(
+ body: new AddRemoteForm(
trans,
'Enter remote repository name and url',
'',
diff --git a/src/model.ts b/src/model.ts
index 067f78618..b273e64a5 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -453,6 +453,21 @@ export class GitExtension implements IGitExtension {
});
}
+ async getRemotes(): Promise {
+ //const path = await this._getPathRepository();
+ const remotes: Git.IGitRemote[] = [
+ {
+ name: 'origin',
+ url: 'https://github.com/BoscoCHW/test_private_repo_2.git'
+ },
+ {
+ name: 'git',
+ url: 'git@github.com:jupyterlab/jupyterlab-git.git'
+ }
+ ];
+ return remotes;
+ }
+
/**
* Retrieve the repository commit log.
*
diff --git a/src/widgets/GitAddRemoteForm.tsx b/src/widgets/AddRemoteForm.tsx
similarity index 60%
rename from src/widgets/GitAddRemoteForm.tsx
rename to src/widgets/AddRemoteForm.tsx
index c98072180..f9e6bb5f7 100644
--- a/src/widgets/GitAddRemoteForm.tsx
+++ b/src/widgets/AddRemoteForm.tsx
@@ -7,7 +7,7 @@ import { GitExtension } from '../model';
/**
* The UI for the add remote repository form
*/
-export class GitAddRemoteForm
+export class AddRemoteForm
extends Widget
implements Dialog.IBodyWidget
{
@@ -20,11 +20,12 @@ export class GitAddRemoteForm
super();
this._trans = trans;
this._model = model;
- this.node.appendChild(this.createBody(textContent, warningContent));
+ this._addRemoteFormContainer = this.createBody(textContent, warningContent);
+ this.node.appendChild(this._addRemoteFormContainer);
+ this._showRemotes();
}
private createBody(textContent: string, warningContent: string): HTMLElement {
- console.log(this._model);
const node = document.createElement('div');
node.className = 'jp-AddRemoteBox';
@@ -53,6 +54,31 @@ export class GitAddRemoteForm
return node;
}
+ private async _showRemotes(): Promise {
+ const remotes: Git.IGitRemote[] = await this._model.getRemotes();
+
+ const existingRemotesWrapper = document.createElement('div');
+ existingRemotesWrapper.className = 'jp-existing-remotes-wrapper';
+ const existingRemotesHeader = document.createElement('div');
+ existingRemotesHeader.textContent = 'Existing remotes:';
+ existingRemotesWrapper.appendChild(existingRemotesHeader);
+
+ const remoteList = document.createElement('ul');
+ remoteList.className = 'jp-remote-list';
+ remotes.forEach(remote => {
+ const { name, url } = remote;
+ const container = document.createElement('li');
+ container.innerHTML = `
+ ${name}
+ ${url}
+ `;
+ remoteList.appendChild(container);
+ });
+
+ existingRemotesWrapper.appendChild(remoteList);
+ this._addRemoteFormContainer.appendChild(existingRemotesWrapper);
+ }
+
/**
* Returns the input value.
*/
@@ -63,6 +89,7 @@ export class GitAddRemoteForm
};
}
protected _trans: TranslationBundle;
+ private _addRemoteFormContainer: HTMLElement;
private _model: GitExtension;
private _url: HTMLInputElement;
private _name: HTMLInputElement;
diff --git a/style/add-remote-box.css b/style/add-remote-box.css
index b52968689..6eb33be4a 100644
--- a/style/add-remote-box.css
+++ b/style/add-remote-box.css
@@ -5,6 +5,24 @@
margin-bottom: 10px;
}
+.jp-existing-remotes-wrapper {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+.jp-remote-list {
+ list-style: none;
+ margin-top: 5px;
+ padding: 0px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 3px;
+}
+
+.jp-remote-item {
+
+}
+
jp-AddRemoteBox-warning {
color: var(--jp-warn-color0);
}
\ No newline at end of file
From ae6785faf06940430701e87692f95fdb4bf93624 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Tue, 12 Jul 2022 19:17:02 -0700
Subject: [PATCH 05/29] Implement backend api to show remote url info
---
jupyterlab_git/git.py | 21 +++++++++++++++++++++
jupyterlab_git/handlers.py | 16 ++++++++++++++++
src/model.ts | 21 ++++++++++-----------
src/tokens.ts | 10 ++++++++++
style/add-remote-box.css | 3 ---
5 files changed, 57 insertions(+), 14 deletions(-)
diff --git a/jupyterlab_git/git.py b/jupyterlab_git/git.py
index 66b1f5bae..36ca03a92 100644
--- a/jupyterlab_git/git.py
+++ b/jupyterlab_git/git.py
@@ -1512,6 +1512,27 @@ async def remote_show(self, path):
return response
+ async def remote_show_details(self, path):
+ """Handle call to `git remote -v show` command.
+ Args:
+ path (str): Git repository path
+
+ Returns:
+ List[Tuple(str, str)]: Known remotes (name and url)
+ """
+ command = ["git", "remote", "-v", "show"]
+ code, output, error = await execute(command, cwd=path)
+ response = {"code": code, "command": " ".join(command)}
+ if code == 0:
+ response["remotes"] = [
+ {"name": r.split("\t")[0], "url": r.split("\t")[1]}
+ for r in output.splitlines()
+ ]
+ else:
+ response["message"] = error
+
+ return response
+
async def ensure_gitignore(self, path):
"""Handle call to ensure .gitignore file exists and the
next append will be on a new line (this means an empty file
diff --git a/jupyterlab_git/handlers.py b/jupyterlab_git/handlers.py
index 769f93516..55bb7179d 100644
--- a/jupyterlab_git/handlers.py
+++ b/jupyterlab_git/handlers.py
@@ -391,6 +391,21 @@ async def post(self, path: str = ""):
self.finish(json.dumps(output))
+class GitRemoteShowDetailsHandler(GitHandler):
+ """Handler for 'git remote -v'."""
+
+ @tornado.web.authenticated
+ async def get(self, path: str = ""):
+ """GET request handler to retrieve existing remotes."""
+ local_path = self.url2localpath(path)
+ output = await self.git.remote_show_details(local_path)
+ if output["code"] == 0:
+ self.set_status(201)
+ else:
+ self.set_status(500)
+ self.finish(json.dumps(output))
+
+
class GitResetHandler(GitHandler):
"""
Handler for 'git reset '.
@@ -871,6 +886,7 @@ def setup_handlers(web_app):
("/push", GitPushHandler),
("/remote/add", GitRemoteAddHandler),
("/remote/fetch", GitFetchHandler),
+ ("/remote/show", GitRemoteShowDetailsHandler),
("/reset", GitResetHandler),
("/reset_to_commit", GitResetToCommitHandler),
("/show_prefix", GitShowPrefixHandler),
diff --git a/src/model.ts b/src/model.ts
index b273e64a5..aa359ec74 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -454,18 +454,17 @@ export class GitExtension implements IGitExtension {
}
async getRemotes(): Promise {
- //const path = await this._getPathRepository();
- const remotes: Git.IGitRemote[] = [
- {
- name: 'origin',
- url: 'https://github.com/BoscoCHW/test_private_repo_2.git'
- },
- {
- name: 'git',
- url: 'git@github.com:jupyterlab/jupyterlab-git.git'
+ const path = await this._getPathRepository();
+ const result = await this._taskHandler.execute(
+ 'git:show:remote',
+ async () => {
+ return await requestAPI(
+ URLExt.join(path, 'remote', 'show'),
+ 'GET'
+ );
}
- ];
- return remotes;
+ );
+ return result.remotes;
}
/**
diff --git a/src/tokens.ts b/src/tokens.ts
index c654bb3e6..2f410ff1c 100644
--- a/src/tokens.ts
+++ b/src/tokens.ts
@@ -999,6 +999,16 @@ export namespace Git {
name: string;
}
+ /**
+ * Interface for GitRemoteShowDetails request result,
+ * has the name and urls of all remotes
+ */
+ export interface IGitRemoteResult {
+ code: number;
+ command: string;
+ remotes: Git.IGitRemote[];
+ }
+
/**
* Structure for the request to the Git Clone API.
*/
diff --git a/style/add-remote-box.css b/style/add-remote-box.css
index 6eb33be4a..456f1273d 100644
--- a/style/add-remote-box.css
+++ b/style/add-remote-box.css
@@ -19,9 +19,6 @@
row-gap: 3px;
}
-.jp-remote-item {
-
-}
jp-AddRemoteBox-warning {
color: var(--jp-warn-color0);
From f2287fd2144bfb0f767276a84044f7d0645f22d6 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Wed, 13 Jul 2022 11:43:32 -0700
Subject: [PATCH 06/29] Refactor remote_show function to handle verbose case
---
jupyterlab_git/git.py | 37 ++++++++++++++-----------------------
jupyterlab_git/handlers.py | 6 +++---
2 files changed, 17 insertions(+), 26 deletions(-)
diff --git a/jupyterlab_git/git.py b/jupyterlab_git/git.py
index 36ca03a92..9da35df15 100644
--- a/jupyterlab_git/git.py
+++ b/jupyterlab_git/git.py
@@ -1494,40 +1494,31 @@ async def remote_add(self, path, url, name=DEFAULT_REMOTE_NAME):
return response
- async def remote_show(self, path):
+ async def remote_show(self, path, verbose=False):
"""Handle call to `git remote show` command.
Args:
path (str): Git repository path
-
+ verbose (bool): true if details are needed, otherwise, false
Returns:
List[str]: Known remotes
"""
- command = ["git", "remote", "show"]
- code, output, error = await execute(command, cwd=path)
- response = {"code": code, "command": " ".join(command)}
- if code == 0:
- response["remotes"] = [r.strip() for r in output.splitlines()]
+ command = ["git", "remote"]
+ if verbose:
+ command.extend(["-v", "show"])
else:
- response["message"] = error
-
- return response
+ command.append("show")
- async def remote_show_details(self, path):
- """Handle call to `git remote -v show` command.
- Args:
- path (str): Git repository path
-
- Returns:
- List[Tuple(str, str)]: Known remotes (name and url)
- """
- command = ["git", "remote", "-v", "show"]
code, output, error = await execute(command, cwd=path)
response = {"code": code, "command": " ".join(command)}
+
if code == 0:
- response["remotes"] = [
- {"name": r.split("\t")[0], "url": r.split("\t")[1]}
- for r in output.splitlines()
- ]
+ if verbose:
+ response["remotes"] = [
+ {"name": r.split("\t")[0], "url": r.split("\t")[1]}
+ for r in output.splitlines()
+ ]
+ else:
+ response["remotes"] = [r.strip() for r in output.splitlines()]
else:
response["message"] = error
diff --git a/jupyterlab_git/handlers.py b/jupyterlab_git/handlers.py
index 55bb7179d..207f0d786 100644
--- a/jupyterlab_git/handlers.py
+++ b/jupyterlab_git/handlers.py
@@ -391,14 +391,14 @@ async def post(self, path: str = ""):
self.finish(json.dumps(output))
-class GitRemoteShowDetailsHandler(GitHandler):
+class GitRemoteDetailsShowHandler(GitHandler):
"""Handler for 'git remote -v'."""
@tornado.web.authenticated
async def get(self, path: str = ""):
"""GET request handler to retrieve existing remotes."""
local_path = self.url2localpath(path)
- output = await self.git.remote_show_details(local_path)
+ output = await self.git.remote_show(local_path, verbose=True)
if output["code"] == 0:
self.set_status(201)
else:
@@ -886,7 +886,7 @@ def setup_handlers(web_app):
("/push", GitPushHandler),
("/remote/add", GitRemoteAddHandler),
("/remote/fetch", GitFetchHandler),
- ("/remote/show", GitRemoteShowDetailsHandler),
+ ("/remote/show", GitRemoteDetailsShowHandler),
("/reset", GitResetHandler),
("/reset_to_commit", GitResetToCommitHandler),
("/show_prefix", GitShowPrefixHandler),
From 5af1c3bc60345ab139ae974622762d0bbabb9d71 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Thu, 14 Jul 2022 14:51:15 -0700
Subject: [PATCH 07/29] Implement remote dialog box using React
---
src/commandsAndMenu.tsx | 40 ++++---
src/components/AddRemoteDialogue.tsx | 155 +++++++++++++++++++++++++++
src/style/AddRemoteDialog.ts | 11 ++
src/widgets/AddRemoteForm.tsx | 96 -----------------
style/add-remote-box.css | 25 -----
style/base.css | 1 -
6 files changed, 193 insertions(+), 135 deletions(-)
create mode 100644 src/components/AddRemoteDialogue.tsx
create mode 100644 src/style/AddRemoteDialog.ts
delete mode 100644 src/widgets/AddRemoteForm.tsx
delete mode 100644 style/add-remote-box.css
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index 5fcd54c4d..7ce257ba6 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -46,7 +46,7 @@ import {
} from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
-import { AddRemoteForm } from './widgets/AddRemoteForm';
+import { AddRemoteDialogue } from './components/AddRemoteDialogue';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
export interface IGitCloneArgs {
@@ -268,19 +268,33 @@ export function addCommands(
let name = args['name'] as string;
if (!url) {
- const remoteRepo = await showDialog({
- title: trans.__('Add a remote repository'),
- body: new AddRemoteForm(
- trans,
- 'Enter remote repository name and url',
- '',
- gitModel
- )
- });
+ const widgetId = 'git-dialog-AddRemote';
+ let anchor = document.querySelector(`#${widgetId}`);
+ if (!anchor) {
+ anchor = document.createElement('div');
+ anchor.id = widgetId;
+ document.body.appendChild(anchor);
+ }
+
+ const waitForDialog = new PromiseDelegate();
+ const dialog = ReactWidget.create(
+ {
+ dialog.dispose();
+ waitForDialog.resolve(remote ?? null);
+ }}
+ />
+ );
+
+ Widget.attach(dialog, anchor);
+
+ const remote = await waitForDialog.promise;
- if (remoteRepo.button.accept) {
- name = remoteRepo.value.name;
- url = remoteRepo.value.url;
+ if (remote) {
+ name = remote.name;
+ url = remote.url;
}
}
diff --git a/src/components/AddRemoteDialogue.tsx b/src/components/AddRemoteDialogue.tsx
new file mode 100644
index 000000000..a7f85d086
--- /dev/null
+++ b/src/components/AddRemoteDialogue.tsx
@@ -0,0 +1,155 @@
+import * as React from 'react';
+import ClearIcon from '@material-ui/icons/Clear';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import { Git } from '../tokens';
+import { GitExtension } from '../model';
+import { remoteDialogClass } from '../style/AddRemoteDialog';
+import { TranslationBundle } from '@jupyterlab/translation';
+
+import { classes } from 'typestyle';
+import {
+ actionsWrapperClass,
+ buttonClass,
+ cancelButtonClass,
+ closeButtonClass,
+ contentWrapperClass,
+ createButtonClass,
+ titleClass,
+ titleWrapperClass
+} from '../style/NewBranchDialog';
+
+export interface IAddRemoteDialogueProps {
+ /**
+ * The application language translator.
+ */
+ trans: TranslationBundle;
+ warningContent?: string;
+ model: GitExtension;
+ onClose: (remote?: Git.IGitRemote) => void;
+}
+
+export interface IAddRemoteDialogueState {
+ newRemote: Git.IGitRemote;
+ existingRemotes: Git.IGitRemote[];
+}
+
+export class AddRemoteDialogue extends React.Component<
+ IAddRemoteDialogueProps,
+ IAddRemoteDialogueState
+> {
+ constructor(props: IAddRemoteDialogueProps) {
+ super(props);
+ this.state = {
+ newRemote: {
+ name: '',
+ url: ''
+ },
+ existingRemotes: []
+ };
+ }
+
+ async componentDidMount(): Promise {
+ const remotes = await this.props.model.getRemotes();
+ this.setState({ existingRemotes: remotes });
+ }
+
+ render(): JSX.Element {
+ return (
+
+ );
+ }
+}
diff --git a/src/style/AddRemoteDialog.ts b/src/style/AddRemoteDialog.ts
new file mode 100644
index 000000000..0238a5ae5
--- /dev/null
+++ b/src/style/AddRemoteDialog.ts
@@ -0,0 +1,11 @@
+import { style } from 'typestyle';
+
+export const remoteDialogClass = style({
+ width: '400px',
+
+ color: 'var(--jp-ui-font-color1)!important',
+
+ borderRadius: '3px!important',
+
+ backgroundColor: 'var(--jp-layout-color1)!important'
+});
diff --git a/src/widgets/AddRemoteForm.tsx b/src/widgets/AddRemoteForm.tsx
deleted file mode 100644
index f9e6bb5f7..000000000
--- a/src/widgets/AddRemoteForm.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { Dialog } from '@jupyterlab/apputils';
-import { TranslationBundle } from '@jupyterlab/translation';
-import { Widget } from '@lumino/widgets';
-import { Git } from '../tokens';
-import { GitExtension } from '../model';
-
-/**
- * The UI for the add remote repository form
- */
-export class AddRemoteForm
- extends Widget
- implements Dialog.IBodyWidget
-{
- constructor(
- trans: TranslationBundle,
- textContent = trans.__('Enter remote repository name and url'),
- warningContent = '',
- model: GitExtension
- ) {
- super();
- this._trans = trans;
- this._model = model;
- this._addRemoteFormContainer = this.createBody(textContent, warningContent);
- this.node.appendChild(this._addRemoteFormContainer);
- this._showRemotes();
- }
-
- private createBody(textContent: string, warningContent: string): HTMLElement {
- const node = document.createElement('div');
- node.className = 'jp-AddRemoteBox';
-
- const label = document.createElement('label');
-
- const text = document.createElement('span');
- text.textContent = textContent;
- this._name = document.createElement('input');
- this._name.type = 'text';
- this._name.placeholder = this._trans.__('name');
- this._url = document.createElement('input');
- this._url.type = 'text';
- this._url.placeholder = this._trans.__('Remote GIt repository URL');
-
- label.appendChild(text);
- label.appendChild(this._name);
- label.appendChild(this._url);
-
- const warning = document.createElement('div');
- warning.className = 'jp-AddRemoteBox-warning';
- warning.textContent = warningContent;
-
- node.appendChild(label);
- node.appendChild(warning);
-
- return node;
- }
-
- private async _showRemotes(): Promise {
- const remotes: Git.IGitRemote[] = await this._model.getRemotes();
-
- const existingRemotesWrapper = document.createElement('div');
- existingRemotesWrapper.className = 'jp-existing-remotes-wrapper';
- const existingRemotesHeader = document.createElement('div');
- existingRemotesHeader.textContent = 'Existing remotes:';
- existingRemotesWrapper.appendChild(existingRemotesHeader);
-
- const remoteList = document.createElement('ul');
- remoteList.className = 'jp-remote-list';
- remotes.forEach(remote => {
- const { name, url } = remote;
- const container = document.createElement('li');
- container.innerHTML = `
- ${name}
- ${url}
- `;
- remoteList.appendChild(container);
- });
-
- existingRemotesWrapper.appendChild(remoteList);
- this._addRemoteFormContainer.appendChild(existingRemotesWrapper);
- }
-
- /**
- * Returns the input value.
- */
- getValue(): Git.IGitRemote {
- return {
- url: this._url.value,
- name: this._name.value
- };
- }
- protected _trans: TranslationBundle;
- private _addRemoteFormContainer: HTMLElement;
- private _model: GitExtension;
- private _url: HTMLInputElement;
- private _name: HTMLInputElement;
-}
diff --git a/style/add-remote-box.css b/style/add-remote-box.css
deleted file mode 100644
index 456f1273d..000000000
--- a/style/add-remote-box.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.jp-AddRemoteBox input {
- display: block;
- width: 100%;
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
-.jp-existing-remotes-wrapper {
- margin-top: 20px;
- margin-bottom: 10px;
-}
-
-.jp-remote-list {
- list-style: none;
- margin-top: 5px;
- padding: 0px;
- display: flex;
- flex-direction: column;
- row-gap: 3px;
-}
-
-
-jp-AddRemoteBox-warning {
- color: var(--jp-warn-color0);
-}
\ No newline at end of file
diff --git a/style/base.css b/style/base.css
index 8be69086e..0a1857c04 100644
--- a/style/base.css
+++ b/style/base.css
@@ -9,4 +9,3 @@
@import url('diff-text.css');
@import url('variables.css');
@import url('status-widget.css');
-@import url('add-remote-box.css');
From eda196af7cc90867ee1a298e1ecb1731725ede4d Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Thu, 14 Jul 2022 15:42:45 -0700
Subject: [PATCH 08/29] Style remote dialog
---
src/components/AddRemoteDialogue.tsx | 17 +++++++++++------
src/style/AddRemoteDialog.ts | 28 ++++++++++++++++++++++++++--
2 files changed, 37 insertions(+), 8 deletions(-)
diff --git a/src/components/AddRemoteDialogue.tsx b/src/components/AddRemoteDialogue.tsx
index a7f85d086..d38da004a 100644
--- a/src/components/AddRemoteDialogue.tsx
+++ b/src/components/AddRemoteDialogue.tsx
@@ -4,7 +4,12 @@ import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import { Git } from '../tokens';
import { GitExtension } from '../model';
-import { remoteDialogClass } from '../style/AddRemoteDialog';
+import {
+ remoteDialogClass,
+ remoteDialogInputClass,
+ existingRemoteListClass,
+ existingRemoteItemClass
+} from '../style/AddRemoteDialog';
import { TranslationBundle } from '@jupyterlab/translation';
import { classes } from 'typestyle';
@@ -76,10 +81,10 @@ export class AddRemoteDialogue extends React.Component<
-
diff --git a/src/style/AddRemoteDialog.ts b/src/style/AddRemoteDialog.ts
index 215384ec7..51041339f 100644
--- a/src/style/AddRemoteDialog.ts
+++ b/src/style/AddRemoteDialog.ts
@@ -19,14 +19,15 @@ export const remoteDialogInputClass = style({
}
});
-export const existingRemoteListClass = style({
+export const existingRemoteWrapperClass = style({
marginTop: '1.5rem',
- listStyle: 'none',
padding: '0px'
});
-export const existingRemoteItemClass = style({
+export const existingRemoteGridClass = style({
marginTop: '2px',
- display: 'flex',
- columnGap: '5px'
+ display: 'grid',
+ rowGap: '5px',
+ columnGap: '10px',
+ gridTemplateColumns: 'auto auto auto'
});
From 99df0f31bd27175bfbe3eea7b9a8bc25c6d6ef69 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 22 Jul 2022 11:00:31 -0700
Subject: [PATCH 17/29] Fix git remote remove route bug
---
jupyterlab_git/handlers.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/jupyterlab_git/handlers.py b/jupyterlab_git/handlers.py
index 8a0d2c28f..30edbdc5a 100644
--- a/jupyterlab_git/handlers.py
+++ b/jupyterlab_git/handlers.py
@@ -410,17 +410,16 @@ class GitRemoteRemoveHandler(GitHandler):
"""Handler for 'git remote remove '."""
@tornado.web.authenticated
- async def delete(self, path: str = ""):
+ async def delete(self, path: str = "", name: str = ""):
"""DELETE request handler to remove a remote."""
local_path = self.url2localpath(path)
- name = self.path_kwargs.get("name", "")
output = await self.git.remote_remove(local_path, name)
if output["code"] == 0:
self.set_status(204)
else:
self.set_status(500)
- self.finish(json.dumps(output))
+ self.finish(json.dumps(output))
class GitResetHandler(GitHandler):
From 5e505c00d915d9185f2bf3d0975325a373bf5d6f Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 22 Jul 2022 12:49:24 -0700
Subject: [PATCH 18/29] Implement advanced push dialog box
---
schema/plugin.json | 2 +-
src/commandsAndMenu.tsx | 26 ++---
src/widgets/AdvancedPushForm.tsx | 103 ++++++++++++++++++
src/widgets/SelectRemoteForm.tsx | 70 ------------
...ection-form.css => advanced-push-form.css} | 11 +-
style/base.css | 2 +-
6 files changed, 126 insertions(+), 88 deletions(-)
create mode 100644 src/widgets/AdvancedPushForm.tsx
delete mode 100644 src/widgets/SelectRemoteForm.tsx
rename style/{single-selection-form.css => advanced-push-form.css} (62%)
diff --git a/schema/plugin.json b/schema/plugin.json
index ad8528588..6339889a7 100644
--- a/schema/plugin.json
+++ b/schema/plugin.json
@@ -100,7 +100,7 @@
},
{
"command": "git:push",
- "args": { "force": true }
+ "args": { "advanced": true }
},
{
"command": "git:pull"
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index 936e4e799..7d0560490 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -48,7 +48,7 @@ import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
import { AddRemoteDialogue } from './components/AddRemoteDialogue';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
-import { SingleSelectionForm } from './widgets/SelectRemoteForm';
+import { AdvancedPushForm } from './widgets/AdvancedPushForm';
export interface IGitCloneArgs {
/**
@@ -326,30 +326,28 @@ export function addCommands(
/** Add git push command */
commands.addCommand(CommandIDs.gitPush, {
label: args =>
- args.force
- ? trans.__('Push to Remote (Force)')
+ (args['advanced'] as boolean)
+ ? trans.__('Push to Remote (Advanced)')
: trans.__('Push to Remote'),
caption: trans.__('Push code to remote repository'),
isEnabled: () => gitModel.pathRepository !== null,
execute: async args => {
try {
- const remotes = await gitModel.getRemotes();
-
let remote;
- if (remotes.length > 1) {
+ let force;
+
+ if (args['advanced'] as boolean) {
const result = await showDialog({
- title: trans.__('Pick a remote repository to push.'),
- body: new SingleSelectionForm(
- trans.__(''),
- remotes.map(remote => remote.name)
- ),
+ title: trans.__('Please select push options.'),
+ body: new AdvancedPushForm(trans, gitModel),
buttons: [
Dialog.cancelButton({ label: trans.__('Cancel') }),
Dialog.okButton({ label: trans.__('Proceed') })
]
});
if (result.button.accept) {
- remote = result.value.selection;
+ remote = result.value.remoteName;
+ force = result.value.force;
} else {
return;
}
@@ -361,7 +359,7 @@ export function addCommands(
});
const details = await showGitOperationDialog(
gitModel,
- args.force ? Operation.ForcePush : Operation.Push,
+ force ? Operation.ForcePush : Operation.Push,
trans,
(args = { remote })
);
@@ -1320,7 +1318,7 @@ export function createGitMenu(
].forEach(command => {
menu.addItem({ command });
if (command === CommandIDs.gitPush) {
- menu.addItem({ command, args: { force: true } });
+ menu.addItem({ command, args: { advanced: true } });
}
if (command === CommandIDs.gitPull) {
menu.addItem({ command, args: { force: true } });
diff --git a/src/widgets/AdvancedPushForm.tsx b/src/widgets/AdvancedPushForm.tsx
new file mode 100644
index 000000000..86446248c
--- /dev/null
+++ b/src/widgets/AdvancedPushForm.tsx
@@ -0,0 +1,103 @@
+import { Dialog } from '@jupyterlab/apputils';
+import { Widget } from '@lumino/widgets';
+import { GitExtension } from '../model';
+import { TranslationBundle } from '@jupyterlab/translation';
+
+/**
+ * Interface for returned value from dialog box
+ */
+export interface IAdvancedPushFormValue {
+ remoteName: string;
+ force: boolean;
+}
+
+/**
+ * A widget form with advanced push options,
+ * can be used as a Dialog body.
+ */
+export class AdvancedPushForm
+ extends Widget
+ implements Dialog.IBodyWidget
+{
+ constructor(trans: TranslationBundle, model: GitExtension) {
+ super();
+ this._trans = trans;
+ this._model = model;
+ this._radioButtons = [];
+ this.node.appendChild(this.createBody());
+ this.addRemoteOptions();
+ }
+
+ private createBody(): HTMLElement {
+ const mainNode = document.createElement('div');
+
+ // Instructional text
+ const text = document.createElement('div');
+ text.textContent = this._trans.__('Choose a remote to push to.');
+
+ // List of remotes
+ const remoteOptionsContainer = document.createElement('div');
+ remoteOptionsContainer.className = 'jp-remote-options-wrapper';
+ this._remoteOptionsContainer = remoteOptionsContainer;
+
+ // Force option
+ const forceCheckboxContainer = document.createElement('label');
+ forceCheckboxContainer.className = 'jp-force-box-container';
+
+ this._forceCheckbox = document.createElement('input');
+ this._forceCheckbox.type = 'checkbox';
+ this._forceCheckbox.checked = false;
+
+ const label = document.createElement('span');
+ label.textContent = this._trans.__('Force Push');
+
+ forceCheckboxContainer.appendChild(this._forceCheckbox);
+ forceCheckboxContainer.appendChild(label);
+
+ mainNode.appendChild(text);
+ mainNode.appendChild(remoteOptionsContainer);
+ mainNode.appendChild(forceCheckboxContainer);
+
+ return mainNode;
+ }
+
+ private async addRemoteOptions(): Promise {
+ const remotes = await this._model.getRemotes();
+
+ remotes.forEach(remote => {
+ const buttonWrapper = document.createElement('div');
+ buttonWrapper.className = 'jp-button-wrapper';
+ const radioButton = document.createElement('input');
+ radioButton.type = 'radio';
+ radioButton.id = remote.name;
+ radioButton.value = remote.name;
+ radioButton.name = 'option';
+ radioButton.className = 'jp-option';
+ if (remote.name === 'origin') {
+ radioButton.checked = true;
+ }
+ this._radioButtons.push(radioButton);
+
+ const label = document.createElement('label');
+ label.htmlFor = remote.name;
+ label.textContent = remote.name;
+
+ buttonWrapper.appendChild(radioButton);
+ buttonWrapper.appendChild(label);
+ this._remoteOptionsContainer.appendChild(buttonWrapper);
+ });
+ }
+
+ getValue(): IAdvancedPushFormValue {
+ return {
+ remoteName: this._radioButtons.find(rb => rb.checked).value,
+ force: this._forceCheckbox.checked
+ };
+ }
+
+ private _trans: TranslationBundle;
+ private _model: GitExtension;
+ private _remoteOptionsContainer: HTMLElement;
+ private _radioButtons: HTMLInputElement[];
+ private _forceCheckbox: HTMLInputElement;
+}
diff --git a/src/widgets/SelectRemoteForm.tsx b/src/widgets/SelectRemoteForm.tsx
deleted file mode 100644
index 5f4891dd6..000000000
--- a/src/widgets/SelectRemoteForm.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Dialog } from '@jupyterlab/apputils';
-import { Widget } from '@lumino/widgets';
-
-/**
- * Interface for returned value from dialog box
- */
-export interface ISingleSelectionFormValue {
- selection: string;
-}
-
-/**
- * A widget form containing a text block and a list of options,
- * can be used as a Dialog body.
- */
-export class SingleSelectionForm
- extends Widget
- implements Dialog.IBodyWidget
-{
- constructor(textBody: string, options: string[]) {
- super();
- this._radioButtons = [];
- this.node.appendChild(this.createBody(textBody, options));
- }
-
- private createBody(textBody: string, options: string[]): HTMLElement {
- const mainNode = document.createElement('div');
-
- const text = document.createElement('div');
- text.textContent = textBody;
-
- const optionsContainer = document.createElement('div');
- optionsContainer.className = 'jp-options-wrapper';
-
- options.forEach(option => {
- const buttonWrapper = document.createElement('div');
- buttonWrapper.className = 'jp-button-wrapper';
- const radioButton = document.createElement('input');
- radioButton.type = 'radio';
- radioButton.id = option;
- radioButton.value = option;
- radioButton.name = 'option';
- radioButton.className = 'jp-option';
- if (option === 'origin') {
- radioButton.checked = true;
- }
- this._radioButtons.push(radioButton);
-
- const label = document.createElement('label');
- label.htmlFor = option;
- label.textContent = option;
-
- buttonWrapper.appendChild(radioButton);
- buttonWrapper.appendChild(label);
- optionsContainer.appendChild(buttonWrapper);
- });
-
- mainNode.appendChild(text);
- mainNode.appendChild(optionsContainer);
-
- return mainNode;
- }
-
- getValue(): ISingleSelectionFormValue {
- return {
- selection: this._radioButtons.find(rb => rb.checked).value
- };
- }
-
- private _radioButtons: HTMLInputElement[];
-}
diff --git a/style/single-selection-form.css b/style/advanced-push-form.css
similarity index 62%
rename from style/single-selection-form.css
rename to style/advanced-push-form.css
index c245ff793..b308fae83 100644
--- a/style/single-selection-form.css
+++ b/style/advanced-push-form.css
@@ -1,4 +1,5 @@
-.jp-options-wrapper {
+.jp-remote-options-wrapper {
+ margin: 4px;
display: flex;
flex-direction: column;
align-items: stretch;
@@ -15,5 +16,11 @@
height: fit-content !important;
appearance: auto !important;
margin: 0px;
- padding: 0px;
+}
+
+.jp-force-box-container {
+ margin-top: 1rem;
+ display: flex;
+ align-items: flex-end;
+ column-gap: 5px;
}
\ No newline at end of file
diff --git a/style/base.css b/style/base.css
index f3b19d248..a7596bc69 100644
--- a/style/base.css
+++ b/style/base.css
@@ -9,4 +9,4 @@
@import url('diff-text.css');
@import url('variables.css');
@import url('status-widget.css');
-@import url('single-selection-form.css');
+@import url('advanced-push-form.css');
From f077fc045b2a4996be4c35760cbf580f51d5c2df Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 22 Jul 2022 12:57:24 -0700
Subject: [PATCH 19/29] Show remote url in advanced push dialog and increase
text font size
---
src/widgets/AdvancedPushForm.tsx | 3 ++-
style/advanced-push-form.css | 4 ++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/widgets/AdvancedPushForm.tsx b/src/widgets/AdvancedPushForm.tsx
index 86446248c..015371ed9 100644
--- a/src/widgets/AdvancedPushForm.tsx
+++ b/src/widgets/AdvancedPushForm.tsx
@@ -33,6 +33,7 @@ export class AdvancedPushForm
// Instructional text
const text = document.createElement('div');
+ text.className = 'jp-remote-text';
text.textContent = this._trans.__('Choose a remote to push to.');
// List of remotes
@@ -80,7 +81,7 @@ export class AdvancedPushForm
const label = document.createElement('label');
label.htmlFor = remote.name;
- label.textContent = remote.name;
+ label.textContent = `${remote.name}: ${remote.url}`;
buttonWrapper.appendChild(radioButton);
buttonWrapper.appendChild(label);
diff --git a/style/advanced-push-form.css b/style/advanced-push-form.css
index b308fae83..0cc8de83b 100644
--- a/style/advanced-push-form.css
+++ b/style/advanced-push-form.css
@@ -1,3 +1,7 @@
+.jp-remote-text {
+ font-size: 1rem;
+}
+
.jp-remote-options-wrapper {
margin: 4px;
display: flex;
From dd214d7491666216df91b4f8bab50c9269019e57 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 29 Jul 2022 11:48:32 -0700
Subject: [PATCH 20/29] Move dialog action buttons to just below input fields
and display message when loading remotes
---
src/commandsAndMenu.tsx | 4 +-
src/components/AddRemoteDialogue.tsx | 70 +++++++++++++++-------------
src/style/AddRemoteDialog.ts | 7 ++-
3 files changed, 46 insertions(+), 35 deletions(-)
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index 7d0560490..a392fa8cd 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -255,8 +255,8 @@ export function addCommands(
/** Command to add a remote Git repository */
commands.addCommand(CommandIDs.gitAddRemote, {
- label: trans.__('Add Remote Repository'),
- caption: trans.__('Add a Git remote repository'),
+ label: trans.__('Manage Remote Repositories'),
+ caption: trans.__('Manage Remote Repositories'),
isEnabled: () => gitModel.pathRepository !== null,
execute: async args => {
if (gitModel.pathRepository === null) {
diff --git a/src/components/AddRemoteDialogue.tsx b/src/components/AddRemoteDialogue.tsx
index 21e9937f1..d09a56ef6 100644
--- a/src/components/AddRemoteDialogue.tsx
+++ b/src/components/AddRemoteDialogue.tsx
@@ -9,13 +9,13 @@ import {
remoteDialogClass,
remoteDialogInputClass,
existingRemoteWrapperClass,
- existingRemoteGridClass
+ existingRemoteGridClass,
+ actionsWrapperClass
} from '../style/AddRemoteDialog';
import { TranslationBundle } from '@jupyterlab/translation';
import { classes } from 'typestyle';
import {
- actionsWrapperClass,
buttonClass,
cancelButtonClass,
closeButtonClass,
@@ -54,7 +54,7 @@ export interface IAddRemoteDialogueState {
/**
* List of known remotes
*/
- existingRemotes: Git.IGitRemote[];
+ existingRemotes: Git.IGitRemote[] | null;
}
export class AddRemoteDialogue extends React.Component<
@@ -68,7 +68,7 @@ export class AddRemoteDialogue extends React.Component<
name: '',
url: ''
},
- existingRemotes: []
+ existingRemotes: null
};
}
@@ -137,9 +137,36 @@ export class AddRemoteDialogue extends React.Component<
)}
- {this.state.existingRemotes.length > 0 && (
-
-
{this.props.trans.__('Existing Remotes:')}
+
+ {
+ this.props.onClose();
+ }}
+ />
+ {
+ this.props.onClose(this.state.newRemote);
+ }}
+ disabled={!this.state.newRemote.name || !this.state.newRemote.url}
+ />
+
+
+
+
{this.props.trans.__('Existing Remotes:')}
+
+ {this.state.existingRemotes === null ? (
+
Loading remote repositories...
+ ) : this.state.existingRemotes.length > 0 ? (
{this.state.existingRemotes.map((remote, index) => (
<>
@@ -160,32 +187,11 @@ export class AddRemoteDialogue extends React.Component<
>
))}
-
- )}
-
-
- This repository does not have any remote.
)}
- value={this.props.trans.__('Cancel')}
- onClick={() => {
- this.props.onClose();
- }}
- />
- {
- this.props.onClose(this.state.newRemote);
- }}
- disabled={!this.state.newRemote.name || !this.state.newRemote.url}
- />
-
+
+
);
}
diff --git a/src/style/AddRemoteDialog.ts b/src/style/AddRemoteDialog.ts
index 51041339f..a4ee2bd53 100644
--- a/src/style/AddRemoteDialog.ts
+++ b/src/style/AddRemoteDialog.ts
@@ -19,8 +19,13 @@ export const remoteDialogInputClass = style({
}
});
+export const actionsWrapperClass = style({
+ padding: '15px 0px !important',
+ justifyContent: 'space-around !important'
+});
+
export const existingRemoteWrapperClass = style({
- marginTop: '1.5rem',
+ margin: '1.5rem 0rem 1rem',
padding: '0px'
});
From 30fdc4231ff8e3bf2f0c316116c84998c3ba54d0 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 29 Jul 2022 12:34:38 -0700
Subject: [PATCH 21/29] Display loading message when getting remote information
and handle no remote case
---
src/widgets/AdvancedPushForm.tsx | 67 ++++++++++++++++++++------------
style/advanced-push-form.css | 2 +-
2 files changed, 44 insertions(+), 25 deletions(-)
diff --git a/src/widgets/AdvancedPushForm.tsx b/src/widgets/AdvancedPushForm.tsx
index 015371ed9..aa5827f79 100644
--- a/src/widgets/AdvancedPushForm.tsx
+++ b/src/widgets/AdvancedPushForm.tsx
@@ -7,7 +7,13 @@ import { TranslationBundle } from '@jupyterlab/translation';
* Interface for returned value from dialog box
*/
export interface IAdvancedPushFormValue {
+ /**
+ * The name of the remote repository to push to.
+ */
remoteName: string;
+ /**
+ * Whether to use force push.
+ */
force: boolean;
}
@@ -39,6 +45,11 @@ export class AdvancedPushForm
// List of remotes
const remoteOptionsContainer = document.createElement('div');
remoteOptionsContainer.className = 'jp-remote-options-wrapper';
+ const loadingMessage = document.createElement('div');
+ loadingMessage.textContent = this._trans.__(
+ 'Loading remote repositories...'
+ );
+ remoteOptionsContainer.appendChild(loadingMessage);
this._remoteOptionsContainer = remoteOptionsContainer;
// Force option
@@ -64,34 +75,42 @@ export class AdvancedPushForm
private async addRemoteOptions(): Promise {
const remotes = await this._model.getRemotes();
-
- remotes.forEach(remote => {
- const buttonWrapper = document.createElement('div');
- buttonWrapper.className = 'jp-button-wrapper';
- const radioButton = document.createElement('input');
- radioButton.type = 'radio';
- radioButton.id = remote.name;
- radioButton.value = remote.name;
- radioButton.name = 'option';
- radioButton.className = 'jp-option';
- if (remote.name === 'origin') {
- radioButton.checked = true;
- }
- this._radioButtons.push(radioButton);
-
- const label = document.createElement('label');
- label.htmlFor = remote.name;
- label.textContent = `${remote.name}: ${remote.url}`;
-
- buttonWrapper.appendChild(radioButton);
- buttonWrapper.appendChild(label);
- this._remoteOptionsContainer.appendChild(buttonWrapper);
- });
+ this._remoteOptionsContainer.innerHTML = '';
+ if (remotes.length > 0) {
+ remotes.forEach(remote => {
+ const buttonWrapper = document.createElement('div');
+ buttonWrapper.className = 'jp-button-wrapper';
+ const radioButton = document.createElement('input');
+ radioButton.type = 'radio';
+ radioButton.id = remote.name;
+ radioButton.value = remote.name;
+ radioButton.name = 'option';
+ radioButton.className = 'jp-option';
+ if (remote.name === 'origin') {
+ radioButton.checked = true;
+ }
+ this._radioButtons.push(radioButton);
+
+ const label = document.createElement('label');
+ label.htmlFor = remote.name;
+ label.textContent = `${remote.name}: ${remote.url}`;
+
+ buttonWrapper.appendChild(radioButton);
+ buttonWrapper.appendChild(label);
+ this._remoteOptionsContainer.appendChild(buttonWrapper);
+ });
+ } else {
+ const noRemoteMsg = document.createElement('div');
+ noRemoteMsg.textContent = this._trans.__(
+ 'This repository has no known remotes.'
+ );
+ this._remoteOptionsContainer.appendChild(noRemoteMsg);
+ }
}
getValue(): IAdvancedPushFormValue {
return {
- remoteName: this._radioButtons.find(rb => rb.checked).value,
+ remoteName: this._radioButtons.find(rb => rb.checked)?.value,
force: this._forceCheckbox.checked
};
}
diff --git a/style/advanced-push-form.css b/style/advanced-push-form.css
index 0cc8de83b..314f3f7d8 100644
--- a/style/advanced-push-form.css
+++ b/style/advanced-push-form.css
@@ -12,7 +12,7 @@
.jp-button-wrapper {
display: flex;
- gap: 1rem;
+ gap: 0.5rem;
align-items: center;
}
From 386bf3b7ebe7edb7412d7ad36db161314928edca Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Tue, 2 Aug 2022 13:44:56 -0700
Subject: [PATCH 22/29] Add tests for remote_show and remote_remove
---
jupyterlab_git/tests/test_remote.py | 82 ++++++++++++++++++++++++++++-
1 file changed, 80 insertions(+), 2 deletions(-)
diff --git a/jupyterlab_git/tests/test_remote.py b/jupyterlab_git/tests/test_remote.py
index 1ed6b1c07..8e5ccc335 100644
--- a/jupyterlab_git/tests/test_remote.py
+++ b/jupyterlab_git/tests/test_remote.py
@@ -1,11 +1,11 @@
import json
from unittest.mock import patch
-
+import os
import pytest
import tornado
+from jupyterlab_git.git import Git
from jupyterlab_git.handlers import NAMESPACE
-
from .testutils import assert_http_error, maybe_future
@@ -101,3 +101,81 @@ async def test_git_add_remote_failure(mock_execute, jp_fetch, jp_root_dir):
mock_execute.assert_called_once_with(
["git", "remote", "add", "origin", url], cwd=str(local_path)
)
+
+
+@patch("jupyterlab_git.git.execute")
+async def test_git_remote_show(mock_execute, jp_root_dir):
+ # Given
+ local_path = jp_root_dir / "test_path"
+ mock_execute.return_value = maybe_future(
+ (0, os.linesep.join(["origin", "test"]), "")
+ )
+
+ # When
+ output = await Git().remote_show(str(local_path), False)
+
+ # Then
+ command = ["git", "remote", "show"]
+ mock_execute.assert_called_once_with(command, cwd=str(local_path))
+ assert output == {
+ "code": 0,
+ "command": " ".join(command),
+ "remotes": ["origin", "test"],
+ }
+
+
+@patch("jupyterlab_git.git.execute")
+async def test_git_remote_show_verbose(mock_execute, jp_fetch, jp_root_dir):
+ # Given
+ local_path = jp_root_dir / "test_path"
+ url = "http://github.com/myid/myrepository.git"
+ process_output = os.linesep.join(
+ [f"origin\t{url} (fetch)", f"origin\t{url} (push)"]
+ )
+ mock_execute.return_value = maybe_future((0, process_output, ""))
+
+ # When
+ response = await jp_fetch(
+ NAMESPACE,
+ local_path.name,
+ "remote",
+ "show",
+ method="GET",
+ )
+
+ # Then
+ command = ["git", "remote", "-v", "show"]
+ mock_execute.assert_called_once_with(command, cwd=str(local_path))
+
+ assert response.code == 200
+ payload = json.loads(response.body)
+ assert payload == {
+ "code": 0,
+ "command": " ".join(command),
+ "remotes": [
+ {"name": "origin", "url": "http://github.com/myid/myrepository.git"}
+ ],
+ }
+
+
+@patch("jupyterlab_git.git.execute")
+async def test_git_remote_remove(mock_execute, jp_fetch, jp_root_dir):
+ # Given
+ local_path = jp_root_dir / "test_path"
+ mock_execute.return_value = maybe_future((0, "", ""))
+
+ # When
+ name = "origin"
+ response = await jp_fetch(
+ NAMESPACE,
+ local_path.name,
+ "remote",
+ name,
+ method="DELETE",
+ )
+
+ # Then
+ command = ["git", "remote", "remove", name]
+ mock_execute.assert_called_once_with(command, cwd=str(local_path))
+
+ assert response.code == 204
From 94f3f10ea15afe728690f6ee4b4445d5e465f924 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Wed, 3 Aug 2022 10:35:54 -0700
Subject: [PATCH 23/29] Remove cancel button from add remote dialog
---
src/components/AddRemoteDialogue.tsx | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/src/components/AddRemoteDialogue.tsx b/src/components/AddRemoteDialogue.tsx
index d09a56ef6..1096b78e1 100644
--- a/src/components/AddRemoteDialogue.tsx
+++ b/src/components/AddRemoteDialogue.tsx
@@ -17,7 +17,6 @@ import { TranslationBundle } from '@jupyterlab/translation';
import { classes } from 'typestyle';
import {
buttonClass,
- cancelButtonClass,
closeButtonClass,
contentWrapperClass,
createButtonClass,
@@ -138,17 +137,6 @@ export class AddRemoteDialogue extends React.Component<
)}
- {
- this.props.onClose();
- }}
- />
Date: Wed, 3 Aug 2022 12:16:42 -0700
Subject: [PATCH 24/29] Change command gitAddRemote to gitManageRemote Disable
arguments for the command
---
schema/plugin.json | 2 +-
src/commandsAndMenu.tsx | 55 +++++++++++++++++++----------------------
src/tokens.ts | 2 +-
3 files changed, 28 insertions(+), 31 deletions(-)
diff --git a/schema/plugin.json b/schema/plugin.json
index 6339889a7..d88271822 100644
--- a/schema/plugin.json
+++ b/schema/plugin.json
@@ -113,7 +113,7 @@
"command": "git:reset-to-remote"
},
{
- "command": "git:add-remote"
+ "command": "git:manage-remote"
},
{
"command": "git:terminal-command"
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index a392fa8cd..ef8f24a1d 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -254,7 +254,7 @@ export function addCommands(
});
/** Command to add a remote Git repository */
- commands.addCommand(CommandIDs.gitAddRemote, {
+ commands.addCommand(CommandIDs.gitManageRemote, {
label: trans.__('Manage Remote Repositories'),
caption: trans.__('Manage Remote Repositories'),
isEnabled: () => gitModel.pathRepository !== null,
@@ -265,38 +265,35 @@ export function addCommands(
);
return;
}
- let url = args['url'] as string;
- let name = args['name'] as string;
- if (!url) {
- const widgetId = 'git-dialog-AddRemote';
- let anchor = document.querySelector(`#${widgetId}`);
- if (!anchor) {
- anchor = document.createElement('div');
- anchor.id = widgetId;
- document.body.appendChild(anchor);
- }
+ const widgetId = 'git-dialog-AddRemote';
+ let anchor = document.querySelector(`#${widgetId}`);
+ if (!anchor) {
+ anchor = document.createElement('div');
+ anchor.id = widgetId;
+ document.body.appendChild(anchor);
+ }
- const waitForDialog = new PromiseDelegate();
- const dialog = ReactWidget.create(
- {
- dialog.dispose();
- waitForDialog.resolve(remote ?? null);
- }}
- />
- );
+ const waitForDialog = new PromiseDelegate();
+ const dialog = ReactWidget.create(
+ {
+ dialog.dispose();
+ waitForDialog.resolve(remote ?? null);
+ }}
+ />
+ );
- Widget.attach(dialog, anchor);
+ Widget.attach(dialog, anchor);
- const remote = await waitForDialog.promise;
+ const remote = await waitForDialog.promise;
- if (remote) {
- name = remote.name;
- url = remote.url;
- }
+ let name, url;
+ if (remote) {
+ name = remote.name;
+ url = remote.url;
}
if (url) {
@@ -1313,7 +1310,7 @@ export function createGitMenu(
CommandIDs.gitPush,
CommandIDs.gitPull,
CommandIDs.gitResetToRemote,
- CommandIDs.gitAddRemote,
+ CommandIDs.gitManageRemote,
CommandIDs.gitTerminalCommand
].forEach(command => {
menu.addItem({ command });
diff --git a/src/tokens.ts b/src/tokens.ts
index 0ca1db5b9..b57a2f775 100644
--- a/src/tokens.ts
+++ b/src/tokens.ts
@@ -1189,7 +1189,7 @@ export enum CommandIDs {
gitOpenUrl = 'git:open-url',
gitToggleSimpleStaging = 'git:toggle-simple-staging',
gitToggleDoubleClickDiff = 'git:toggle-double-click-diff',
- gitAddRemote = 'git:add-remote',
+ gitManageRemote = 'git:manage-remote',
gitClone = 'git:clone',
gitMerge = 'git:merge',
gitOpenGitignore = 'git:open-gitignore',
From 9f3ee728f067d1328e855a22c36fd2ca608de955 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Wed, 3 Aug 2022 12:28:48 -0700
Subject: [PATCH 25/29] Rename files to reflect command name 'ManageRemote'
---
src/commandsAndMenu.tsx | 6 +++---
...RemoteDialogue.tsx => ManageRemoteDialogue.tsx} | 14 +++++++-------
.../{AddRemoteDialog.ts => ManageRemoteDialog.ts} | 0
3 files changed, 10 insertions(+), 10 deletions(-)
rename src/components/{AddRemoteDialogue.tsx => ManageRemoteDialogue.tsx} (94%)
rename src/style/{AddRemoteDialog.ts => ManageRemoteDialog.ts} (100%)
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index ef8f24a1d..e06af3577 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -46,7 +46,7 @@ import {
} from './tokens';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
-import { AddRemoteDialogue } from './components/AddRemoteDialogue';
+import { ManageRemoteDialogue } from './components/ManageRemoteDialogue';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
import { AdvancedPushForm } from './widgets/AdvancedPushForm';
@@ -266,7 +266,7 @@ export function addCommands(
return;
}
- const widgetId = 'git-dialog-AddRemote';
+ const widgetId = 'git-dialog-ManageRemote';
let anchor = document.querySelector(`#${widgetId}`);
if (!anchor) {
anchor = document.createElement('div');
@@ -276,7 +276,7 @@ export function addCommands(
const waitForDialog = new PromiseDelegate();
const dialog = ReactWidget.create(
- {
diff --git a/src/components/AddRemoteDialogue.tsx b/src/components/ManageRemoteDialogue.tsx
similarity index 94%
rename from src/components/AddRemoteDialogue.tsx
rename to src/components/ManageRemoteDialogue.tsx
index 1096b78e1..a0fe16e17 100644
--- a/src/components/AddRemoteDialogue.tsx
+++ b/src/components/ManageRemoteDialogue.tsx
@@ -11,7 +11,7 @@ import {
existingRemoteWrapperClass,
existingRemoteGridClass,
actionsWrapperClass
-} from '../style/AddRemoteDialog';
+} from '../style/ManageRemoteDialog';
import { TranslationBundle } from '@jupyterlab/translation';
import { classes } from 'typestyle';
@@ -26,7 +26,7 @@ import {
import { trashIcon } from '../style/icons';
-export interface IAddRemoteDialogueProps {
+export interface IManageRemoteDialogueProps {
/**
* The application language translator.
*/
@@ -45,7 +45,7 @@ export interface IAddRemoteDialogueProps {
onClose: (remote?: Git.IGitRemote) => void;
}
-export interface IAddRemoteDialogueState {
+export interface IManageRemoteDialogueState {
/**
* New remote name and url pair
*/
@@ -56,11 +56,11 @@ export interface IAddRemoteDialogueState {
existingRemotes: Git.IGitRemote[] | null;
}
-export class AddRemoteDialogue extends React.Component<
- IAddRemoteDialogueProps,
- IAddRemoteDialogueState
+export class ManageRemoteDialogue extends React.Component<
+ IManageRemoteDialogueProps,
+ IManageRemoteDialogueState
> {
- constructor(props: IAddRemoteDialogueProps) {
+ constructor(props: IManageRemoteDialogueProps) {
super(props);
this.state = {
newRemote: {
diff --git a/src/style/AddRemoteDialog.ts b/src/style/ManageRemoteDialog.ts
similarity index 100%
rename from src/style/AddRemoteDialog.ts
rename to src/style/ManageRemoteDialog.ts
From ecff43a141798ea5de04358bb8ceaf0b940a8565 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Wed, 3 Aug 2022 14:26:01 -0700
Subject: [PATCH 26/29] Refactor manageRemote command to let the dialog handle
the adding and removing of remtoes
---
src/commandsAndMenu.tsx | 28 +------------
src/components/ManageRemoteDialogue.tsx | 55 ++++++++++++++++++++-----
2 files changed, 46 insertions(+), 37 deletions(-)
diff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx
index e06af3577..a9ff31925 100644
--- a/src/commandsAndMenu.tsx
+++ b/src/commandsAndMenu.tsx
@@ -258,7 +258,7 @@ export function addCommands(
label: trans.__('Manage Remote Repositories'),
caption: trans.__('Manage Remote Repositories'),
isEnabled: () => gitModel.pathRepository !== null,
- execute: async args => {
+ execute: () => {
if (gitModel.pathRepository === null) {
console.warn(
trans.__('Not in a Git repository. Unable to add a remote.')
@@ -274,39 +274,15 @@ export function addCommands(
document.body.appendChild(anchor);
}
- const waitForDialog = new PromiseDelegate();
const dialog = ReactWidget.create(
{
- dialog.dispose();
- waitForDialog.resolve(remote ?? null);
- }}
+ onClose={() => dialog.dispose()}
/>
);
Widget.attach(dialog, anchor);
-
- const remote = await waitForDialog.promise;
-
- let name, url;
- if (remote) {
- name = remote.name;
- url = remote.url;
- }
-
- if (url) {
- try {
- await gitModel.addRemote(url, name);
- } catch (error) {
- console.error(error);
- showErrorMessage(
- trans.__('Error when adding remote repository'),
- error
- );
- }
- }
}
});
diff --git a/src/components/ManageRemoteDialogue.tsx b/src/components/ManageRemoteDialogue.tsx
index a0fe16e17..6caa7fcb7 100644
--- a/src/components/ManageRemoteDialogue.tsx
+++ b/src/components/ManageRemoteDialogue.tsx
@@ -1,10 +1,13 @@
import * as React from 'react';
-import { ActionButton } from './ActionButton';
import ClearIcon from '@material-ui/icons/Clear';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
+import { TranslationBundle } from '@jupyterlab/translation';
+import { showErrorMessage } from '@jupyterlab/apputils';
+import { ActionButton } from './ActionButton';
import { Git } from '../tokens';
import { GitExtension } from '../model';
+import { classes } from 'typestyle';
import {
remoteDialogClass,
remoteDialogInputClass,
@@ -12,9 +15,6 @@ import {
existingRemoteGridClass,
actionsWrapperClass
} from '../style/ManageRemoteDialog';
-import { TranslationBundle } from '@jupyterlab/translation';
-
-import { classes } from 'typestyle';
import {
buttonClass,
closeButtonClass,
@@ -23,7 +23,6 @@ import {
titleClass,
titleWrapperClass
} from '../style/NewBranchDialog';
-
import { trashIcon } from '../style/icons';
export interface IManageRemoteDialogueProps {
@@ -42,7 +41,7 @@ export interface IManageRemoteDialogueProps {
/**
* Callback to handle the closing of dialogue
*/
- onClose: (remote?: Git.IGitRemote) => void;
+ onClose: () => void;
}
export interface IManageRemoteDialogueState {
@@ -91,9 +90,7 @@ export class ManageRemoteDialogue extends React.Component<
{
- this.props.onClose();
- }}
+ onClick={() => this.props.onClose()}
/>
@@ -105,6 +102,9 @@ export class ManageRemoteDialogue extends React.Component<
)}
{
+ this._nameInput = node;
+ }}
type="text"
placeholder={this.props.trans.__('name')}
onChange={event =>
@@ -117,6 +117,9 @@ export class ManageRemoteDialogue extends React.Component<
}
/>
{
+ this._urlInput = node;
+ }}
type="text"
placeholder={this.props.trans.__('Remote Git repository URL')}
onChange={event =>
@@ -127,6 +130,11 @@ export class ManageRemoteDialogue extends React.Component<
}
})
}
+ onKeyPress={e => {
+ if (e.key === 'Enter') {
+ this._addRemoteButton.click();
+ }
+ }}
/>
@@ -138,12 +146,33 @@ export class ManageRemoteDialogue extends React.Component<
{
+ this._addRemoteButton = btn;
+ }}
className={classes(buttonClass, createButtonClass)}
type="button"
title={this.props.trans.__('Add Remote')}
value={this.props.trans.__('Add')}
- onClick={() => {
- this.props.onClose(this.state.newRemote);
+ onClick={async () => {
+ const { name, url } = this.state.newRemote;
+ try {
+ await this.props.model.addRemote(url, name);
+ this._nameInput.value = '';
+ this._urlInput.value = '';
+ this.setState(prevState => ({
+ existingRemotes: [
+ ...prevState.existingRemotes,
+ prevState.newRemote
+ ],
+ newRemote: { name: '', url: '' }
+ }));
+ } catch (error) {
+ console.error(error);
+ showErrorMessage(
+ this.props.trans.__('Error when adding remote repository'),
+ error
+ );
+ }
}}
disabled={!this.state.newRemote.name || !this.state.newRemote.url}
/>
@@ -183,4 +212,8 @@ export class ManageRemoteDialogue extends React.Component<
);
}
+
+ private _nameInput: HTMLInputElement;
+ private _urlInput: HTMLInputElement;
+ private _addRemoteButton: HTMLInputElement;
}
From 1618609b79cd28710a7480d480948d603ef65769 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Wed, 3 Aug 2022 15:59:24 -0700
Subject: [PATCH 27/29] Comment out tests for addRemote command
---
tests/commands.spec.tsx | 108 ++++++++++++++++++++--------------------
1 file changed, 54 insertions(+), 54 deletions(-)
diff --git a/tests/commands.spec.tsx b/tests/commands.spec.tsx
index 983d0b3a1..ef94ee13f 100644
--- a/tests/commands.spec.tsx
+++ b/tests/commands.spec.tsx
@@ -58,60 +58,60 @@ describe('git-commands', () => {
);
});
- describe('git:add-remote', () => {
- it('should admit user and name arguments', async () => {
- const name = 'ref';
- const url = 'https://www.mygitserver.com/me/myrepo.git';
- const path = DEFAULT_REPOSITORY_PATH;
-
- mockGit.requestAPI.mockImplementation(
- mockedRequestAPI({
- ...mockResponses,
- 'remote/add': {
- body: () => {
- return { code: 0, command: `git remote add ${name} ${url}` };
- }
- }
- })
- );
-
- model.pathRepository = path;
- await model.ready;
-
- await commands.execute(CommandIDs.gitAddRemote, { url, name });
-
- expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
- url,
- name
- });
- });
-
- it('has optional argument name', async () => {
- const name = 'origin';
- const url = 'https://www.mygitserver.com/me/myrepo.git';
- const path = DEFAULT_REPOSITORY_PATH;
-
- mockGit.requestAPI.mockImplementation(
- mockedRequestAPI({
- ...mockResponses,
- 'remote/add': {
- body: () => {
- return { code: 0, command: `git remote add ${name} ${url}` };
- }
- }
- })
- );
-
- model.pathRepository = path;
- await model.ready;
-
- await commands.execute(CommandIDs.gitAddRemote, { url });
-
- expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
- url
- });
- });
- });
+ // describe('git:manage-remote', () => {
+ // it('should admit user and name arguments', async () => {
+ // const name = 'ref';
+ // const url = 'https://www.mygitserver.com/me/myrepo.git';
+ // const path = DEFAULT_REPOSITORY_PATH;
+
+ // mockGit.requestAPI.mockImplementation(
+ // mockedRequestAPI({
+ // ...mockResponses,
+ // 'remote/add': {
+ // body: () => {
+ // return { code: 0, command: `git remote add ${name} ${url}` };
+ // }
+ // }
+ // })
+ // );
+
+ // model.pathRepository = path;
+ // await model.ready;
+
+ // await commands.execute(CommandIDs.gitManageRemote, { url, name });
+
+ // expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
+ // url,
+ // name
+ // });
+ // });
+
+ // it('has optional argument name', async () => {
+ // const name = 'origin';
+ // const url = 'https://www.mygitserver.com/me/myrepo.git';
+ // const path = DEFAULT_REPOSITORY_PATH;
+
+ // mockGit.requestAPI.mockImplementation(
+ // mockedRequestAPI({
+ // ...mockResponses,
+ // 'remote/add': {
+ // body: () => {
+ // return { code: 0, command: `git remote add ${name} ${url}` };
+ // }
+ // }
+ // })
+ // );
+
+ // model.pathRepository = path;
+ // await model.ready;
+
+ // await commands.execute(CommandIDs.gitManageRemote);
+
+ // expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
+ // url
+ // });
+ // });
+ // });
describe('git:context-discard', () => {
['staged', 'partially-staged', 'unstaged', 'untracked'].forEach(status => {
From 9c686a42352ec012b6e3209404d6fce674ecd0ab Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Fri, 5 Aug 2022 13:18:36 -0700
Subject: [PATCH 28/29] Remove test for git:add-remote command
---
tests/commands.spec.tsx | 55 -----------------------------------------
1 file changed, 55 deletions(-)
diff --git a/tests/commands.spec.tsx b/tests/commands.spec.tsx
index ef94ee13f..50df186ba 100644
--- a/tests/commands.spec.tsx
+++ b/tests/commands.spec.tsx
@@ -58,61 +58,6 @@ describe('git-commands', () => {
);
});
- // describe('git:manage-remote', () => {
- // it('should admit user and name arguments', async () => {
- // const name = 'ref';
- // const url = 'https://www.mygitserver.com/me/myrepo.git';
- // const path = DEFAULT_REPOSITORY_PATH;
-
- // mockGit.requestAPI.mockImplementation(
- // mockedRequestAPI({
- // ...mockResponses,
- // 'remote/add': {
- // body: () => {
- // return { code: 0, command: `git remote add ${name} ${url}` };
- // }
- // }
- // })
- // );
-
- // model.pathRepository = path;
- // await model.ready;
-
- // await commands.execute(CommandIDs.gitManageRemote, { url, name });
-
- // expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
- // url,
- // name
- // });
- // });
-
- // it('has optional argument name', async () => {
- // const name = 'origin';
- // const url = 'https://www.mygitserver.com/me/myrepo.git';
- // const path = DEFAULT_REPOSITORY_PATH;
-
- // mockGit.requestAPI.mockImplementation(
- // mockedRequestAPI({
- // ...mockResponses,
- // 'remote/add': {
- // body: () => {
- // return { code: 0, command: `git remote add ${name} ${url}` };
- // }
- // }
- // })
- // );
-
- // model.pathRepository = path;
- // await model.ready;
-
- // await commands.execute(CommandIDs.gitManageRemote);
-
- // expect(mockGit.requestAPI).toBeCalledWith(`${path}/remote/add`, 'POST', {
- // url
- // });
- // });
- // });
-
describe('git:context-discard', () => {
['staged', 'partially-staged', 'unstaged', 'untracked'].forEach(status => {
[' ', 'M', 'A'].forEach(x => {
From c799be3b7ded4d46235a448e15dc3c6dbc65ae61 Mon Sep 17 00:00:00 2001
From: boscochw_ubuntU_ideapad
Date: Mon, 8 Aug 2022 18:02:45 -0700
Subject: [PATCH 29/29] Add tests for component 'ManageRemoteDialogue'
---
src/components/ManageRemoteDialogue.tsx | 12 +-
.../ManageRemoteDialogue.spec.tsx | 193 ++++++++++++++++++
2 files changed, 201 insertions(+), 4 deletions(-)
create mode 100644 tests/test-components/ManageRemoteDialogue.spec.tsx
diff --git a/src/components/ManageRemoteDialogue.tsx b/src/components/ManageRemoteDialogue.tsx
index 6caa7fcb7..c3eef4348 100644
--- a/src/components/ManageRemoteDialogue.tsx
+++ b/src/components/ManageRemoteDialogue.tsx
@@ -71,8 +71,12 @@ export class ManageRemoteDialogue extends React.Component<
}
async componentDidMount(): Promise {
- const remotes = await this.props.model.getRemotes();
- this.setState({ existingRemotes: remotes });
+ try {
+ const remotes = await this.props.model.getRemotes();
+ this.setState({ existingRemotes: remotes });
+ } catch (err) {
+ console.error(err);
+ }
}
render(): JSX.Element {
@@ -186,7 +190,7 @@ export class ManageRemoteDialogue extends React.Component<
) : this.state.existingRemotes.length > 0 ? (
{this.state.existingRemotes.map((remote, index) => (
- <>
+
{remote.name}
{remote.url}
- >
+
))}
) : (
diff --git a/tests/test-components/ManageRemoteDialogue.spec.tsx b/tests/test-components/ManageRemoteDialogue.spec.tsx
new file mode 100644
index 000000000..8f15923a4
--- /dev/null
+++ b/tests/test-components/ManageRemoteDialogue.spec.tsx
@@ -0,0 +1,193 @@
+// @ts-nocheck
+import { shallow, mount } from 'enzyme';
+import 'jest';
+import * as React from 'react';
+import { ActionButton } from '../../src/components/ActionButton';
+import {
+ ManageRemoteDialogue,
+ IManageRemoteDialogueProps
+} from '../../src/components/ManageRemoteDialogue';
+import * as git from '../../src/git';
+import { GitExtension } from '../../src/model';
+import { createButtonClass } from '../../src/style/NewBranchDialog';
+import {
+ mockedRequestAPI,
+ defaultMockedResponses,
+ DEFAULT_REPOSITORY_PATH
+} from '../utils';
+import ClearIcon from '@material-ui/icons/Clear';
+import { nullTranslator } from '@jupyterlab/translation';
+
+jest.mock('../../src/git');
+jest.mock('@jupyterlab/apputils');
+
+const REMOTES = [
+ {
+ name: 'test',
+ url: 'https://test.com'
+ },
+ {
+ name: 'origin',
+ url: 'https://origin.com'
+ }
+];
+
+async function createModel() {
+ const model = new GitExtension();
+ model.pathRepository = DEFAULT_REPOSITORY_PATH;
+
+ await model.ready;
+ return model;
+}
+
+describe('ManageRemoteDialogue', () => {
+ let model: GitExtension;
+ const trans = nullTranslator.load('jupyterlab_git');
+
+ beforeEach(async () => {
+ jest.restoreAllMocks();
+
+ const mock = git as jest.Mocked;
+ mock.requestAPI.mockImplementation(
+ mockedRequestAPI({
+ responses: {
+ ...defaultMockedResponses,
+ 'remote/add': {
+ body: () => {
+ return { code: 0 };
+ }
+ },
+ 'remote/show': {
+ body: () => {
+ return { code: 0, remotes: REMOTES };
+ }
+ }
+ }
+ })
+ );
+
+ model = await createModel();
+ });
+
+ function createProps(
+ props?: Partial
+ ): IManageRemoteDialogueProps {
+ return {
+ model: model,
+ trans: trans,
+ onClose: () => null,
+ ...props
+ };
+ }
+
+ describe('constructor', () => {
+ it('should return a new instance with initial state', () => {
+ const remoteDialogue = shallow(
+
+ );
+ expect(remoteDialogue.instance()).toBeInstanceOf(ManageRemoteDialogue);
+ const initialState = {
+ newRemote: {
+ name: '',
+ url: ''
+ },
+ existingRemotes: null
+ };
+ expect(remoteDialogue.state()).toEqual(initialState);
+ });
+
+ it('should set the correct state after mounting', async () => {
+ const spyGitGetRemotes = jest.spyOn(GitExtension.prototype, 'getRemotes');
+ const spyComponentDidMount = jest.spyOn(
+ ManageRemoteDialogue.prototype,
+ 'componentDidMount'
+ );
+ const remoteDialogue = shallow(
+
+ );
+ await remoteDialogue.instance().componentDidMount();
+ expect(remoteDialogue.state()).toEqual({
+ newRemote: {
+ name: '',
+ url: ''
+ },
+ existingRemotes: REMOTES
+ });
+ expect(spyGitGetRemotes).toHaveBeenCalledTimes(2);
+ expect(spyComponentDidMount).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ describe('render', () => {
+ it('should display a title for the dialogue "Manage Remotes"', () => {
+ const remoteDialogue = shallow(
+
+ );
+ const node = remoteDialogue.find('p').first();
+ expect(node.text()).toEqual('Manage Remotes');
+ });
+ it('should display a button to close the dialogue', () => {
+ const remoteDialogue = shallow(
+
+ );
+ const nodes = remoteDialogue.find(ClearIcon);
+ expect(nodes.length).toEqual(1);
+ });
+
+ it('should display two input boxes for entering new remote name and url', () => {
+ const remoteDialogue = shallow(
+
+ );
+ const nameInput = remoteDialogue.find('input[placeholder="name"]');
+ const urlInput = remoteDialogue.find(
+ 'input[placeholder="Remote Git repository URL"]'
+ );
+ expect(nameInput.length).toEqual(1);
+ expect(urlInput.length).toEqual(1);
+ });
+
+ it('should display a button to add a new remote', () => {
+ const remoteDialogue = shallow(
+
+ );
+ const node = remoteDialogue.find(`.${createButtonClass}`).first();
+ expect(node.prop('value')).toEqual('Add');
+ });
+
+ it('should display buttons to remove existing remotes', async () => {
+ const remoteDialogue = shallow(
+
+ );
+ await remoteDialogue.instance().componentDidMount();
+ const nodes = remoteDialogue.find(ActionButton);
+ expect(nodes.length).toEqual(REMOTES.length);
+ });
+ });
+
+ describe('functionality', () => {
+ it('should add a new remote', async () => {
+ const remoteDialogue = shallow(
+
+ );
+ const newRemote = {
+ name: 'newRemote',
+ url: 'newremote.com'
+ };
+ await remoteDialogue.setState({
+ newRemote
+ });
+
+ const spyGitAddRemote = jest.spyOn(GitExtension.prototype, 'addRemote');
+ const addRemoteButton = remoteDialogue
+ .find(`.${createButtonClass}`)
+ .first();
+ addRemoteButton.simulate('click');
+
+ expect(spyGitAddRemote).toHaveBeenCalledTimes(1);
+ expect(spyGitAddRemote).toHaveBeenCalledWith(
+ newRemote.url,
+ newRemote.name
+ );
+ });
+ });
+});