Skip to content

Commit 2fc7423

Browse files
BoscoCHWfcollonval
andauthored
Implement ui to support multiple remotes (#1146)
* implement name-url pair for adding remote repo * implement name-url pair for adding remote repo * Style Add Remote form * show existing remote on add remote form * Implement backend api to show remote url info * Refactor remote_show function to handle verbose case * Implement remote dialog box using React * Style remote dialog * Add remove remote button * Implement backend to remove remote * Document codes * Show push remote url only * Implement pushing options for multiple remotes * Change GitRemoteDetailsShowHandler successful code Co-authored-by: Frédéric Collonval <[email protected]> * Attempting to implement the DELETE method for removing a remote * style existing remote list with the grid api * Fix git remote remove route bug * Implement advanced push dialog box * Show remote url in advanced push dialog and increase text font size * Move dialog action buttons to just below input fields and display message when loading remotes * Display loading message when getting remote information and handle no remote case * Add tests for remote_show and remote_remove * Remove cancel button from add remote dialog * Change command gitAddRemote to gitManageRemote Disable arguments for the command * Rename files to reflect command name 'ManageRemote' * Refactor manageRemote command to let the dialog handle the adding and removing of remtoes * Comment out tests for addRemote command * Remove test for git:add-remote command * Add tests for component 'ManageRemoteDialogue' Co-authored-by: Frédéric Collonval <[email protected]>
1 parent 6854bb4 commit 2fc7423

File tree

14 files changed

+898
-114
lines changed

14 files changed

+898
-114
lines changed

jupyterlab_git/git.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,24 +1494,54 @@ async def remote_add(self, path, url, name=DEFAULT_REMOTE_NAME):
14941494

14951495
return response
14961496

1497-
async def remote_show(self, path):
1497+
async def remote_show(self, path, verbose=False):
14981498
"""Handle call to `git remote show` command.
14991499
Args:
15001500
path (str): Git repository path
1501-
1501+
verbose (bool): true if details are needed, otherwise, false
15021502
Returns:
1503-
List[str]: Known remotes
1503+
if not verbose: List[str]: Known remotes
1504+
if verbose: List[ { name: str, url: str } ]: Known remotes
15041505
"""
1505-
command = ["git", "remote", "show"]
1506+
command = ["git", "remote"]
1507+
if verbose:
1508+
command.extend(["-v", "show"])
1509+
else:
1510+
command.append("show")
1511+
15061512
code, output, error = await execute(command, cwd=path)
15071513
response = {"code": code, "command": " ".join(command)}
1514+
15081515
if code == 0:
1509-
response["remotes"] = [r.strip() for r in output.splitlines()]
1516+
if verbose:
1517+
response["remotes"] = [
1518+
{"name": r.split("\t")[0], "url": r.split("\t")[1][:-7]}
1519+
for r in output.splitlines()
1520+
if "(push)" in r
1521+
]
1522+
else:
1523+
response["remotes"] = [r.strip() for r in output.splitlines()]
15101524
else:
15111525
response["message"] = error
15121526

15131527
return response
15141528

1529+
async def remote_remove(self, path, name):
1530+
"""Handle call to `git remote remove <name>` command.
1531+
Args:
1532+
path (str): Git repository path
1533+
name (str): Remote name
1534+
"""
1535+
command = ["git", "remote", "remove", name]
1536+
1537+
code, _, error = await execute(command, cwd=path)
1538+
response = {"code": code, "command": " ".join(command)}
1539+
1540+
if code != 0:
1541+
response["message"] = error
1542+
1543+
return response
1544+
15151545
async def ensure_gitignore(self, path):
15161546
"""Handle call to ensure .gitignore file exists and the
15171547
next append will be on a new line (this means an empty file

jupyterlab_git/handlers.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,37 @@ async def post(self, path: str = ""):
391391
self.finish(json.dumps(output))
392392

393393

394+
class GitRemoteDetailsShowHandler(GitHandler):
395+
"""Handler for 'git remote -v'."""
396+
397+
@tornado.web.authenticated
398+
async def get(self, path: str = ""):
399+
"""GET request handler to retrieve existing remotes."""
400+
local_path = self.url2localpath(path)
401+
output = await self.git.remote_show(local_path, verbose=True)
402+
if output["code"] == 0:
403+
self.set_status(200)
404+
else:
405+
self.set_status(500)
406+
self.finish(json.dumps(output))
407+
408+
409+
class GitRemoteRemoveHandler(GitHandler):
410+
"""Handler for 'git remote remove <name>'."""
411+
412+
@tornado.web.authenticated
413+
async def delete(self, path: str = "", name: str = ""):
414+
"""DELETE request handler to remove a remote."""
415+
local_path = self.url2localpath(path)
416+
417+
output = await self.git.remote_remove(local_path, name)
418+
if output["code"] == 0:
419+
self.set_status(204)
420+
else:
421+
self.set_status(500)
422+
self.finish(json.dumps(output))
423+
424+
394425
class GitResetHandler(GitHandler):
395426
"""
396427
Handler for 'git reset <filename>'.
@@ -871,6 +902,7 @@ def setup_handlers(web_app):
871902
("/push", GitPushHandler),
872903
("/remote/add", GitRemoteAddHandler),
873904
("/remote/fetch", GitFetchHandler),
905+
("/remote/show", GitRemoteDetailsShowHandler),
874906
("/reset", GitResetHandler),
875907
("/reset_to_commit", GitResetToCommitHandler),
876908
("/show_prefix", GitShowPrefixHandler),
@@ -890,12 +922,23 @@ def setup_handlers(web_app):
890922

891923
# add the baseurl to our paths
892924
base_url = web_app.settings["base_url"]
893-
git_handlers = [
894-
(url_path_join(base_url, NAMESPACE + path_regex + endpoint), handler)
895-
for endpoint, handler in handlers_with_path
896-
] + [
897-
(url_path_join(base_url, NAMESPACE + endpoint), handler)
898-
for endpoint, handler in handlers
899-
]
925+
git_handlers = (
926+
[
927+
(url_path_join(base_url, NAMESPACE + path_regex + endpoint), handler)
928+
for endpoint, handler in handlers_with_path
929+
]
930+
+ [
931+
(url_path_join(base_url, NAMESPACE + endpoint), handler)
932+
for endpoint, handler in handlers
933+
]
934+
+ [
935+
(
936+
url_path_join(
937+
base_url, NAMESPACE + path_regex + r"/remote/(?P<name>\w+)"
938+
),
939+
GitRemoteRemoveHandler,
940+
)
941+
]
942+
)
900943

901944
web_app.add_handlers(".*", git_handlers)

jupyterlab_git/tests/test_remote.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import json
22
from unittest.mock import patch
3-
3+
import os
44
import pytest
55
import tornado
66

7+
from jupyterlab_git.git import Git
78
from jupyterlab_git.handlers import NAMESPACE
8-
99
from .testutils import assert_http_error, maybe_future
1010

1111

@@ -101,3 +101,81 @@ async def test_git_add_remote_failure(mock_execute, jp_fetch, jp_root_dir):
101101
mock_execute.assert_called_once_with(
102102
["git", "remote", "add", "origin", url], cwd=str(local_path)
103103
)
104+
105+
106+
@patch("jupyterlab_git.git.execute")
107+
async def test_git_remote_show(mock_execute, jp_root_dir):
108+
# Given
109+
local_path = jp_root_dir / "test_path"
110+
mock_execute.return_value = maybe_future(
111+
(0, os.linesep.join(["origin", "test"]), "")
112+
)
113+
114+
# When
115+
output = await Git().remote_show(str(local_path), False)
116+
117+
# Then
118+
command = ["git", "remote", "show"]
119+
mock_execute.assert_called_once_with(command, cwd=str(local_path))
120+
assert output == {
121+
"code": 0,
122+
"command": " ".join(command),
123+
"remotes": ["origin", "test"],
124+
}
125+
126+
127+
@patch("jupyterlab_git.git.execute")
128+
async def test_git_remote_show_verbose(mock_execute, jp_fetch, jp_root_dir):
129+
# Given
130+
local_path = jp_root_dir / "test_path"
131+
url = "http://github.com/myid/myrepository.git"
132+
process_output = os.linesep.join(
133+
[f"origin\t{url} (fetch)", f"origin\t{url} (push)"]
134+
)
135+
mock_execute.return_value = maybe_future((0, process_output, ""))
136+
137+
# When
138+
response = await jp_fetch(
139+
NAMESPACE,
140+
local_path.name,
141+
"remote",
142+
"show",
143+
method="GET",
144+
)
145+
146+
# Then
147+
command = ["git", "remote", "-v", "show"]
148+
mock_execute.assert_called_once_with(command, cwd=str(local_path))
149+
150+
assert response.code == 200
151+
payload = json.loads(response.body)
152+
assert payload == {
153+
"code": 0,
154+
"command": " ".join(command),
155+
"remotes": [
156+
{"name": "origin", "url": "http://github.com/myid/myrepository.git"}
157+
],
158+
}
159+
160+
161+
@patch("jupyterlab_git.git.execute")
162+
async def test_git_remote_remove(mock_execute, jp_fetch, jp_root_dir):
163+
# Given
164+
local_path = jp_root_dir / "test_path"
165+
mock_execute.return_value = maybe_future((0, "", ""))
166+
167+
# When
168+
name = "origin"
169+
response = await jp_fetch(
170+
NAMESPACE,
171+
local_path.name,
172+
"remote",
173+
name,
174+
method="DELETE",
175+
)
176+
177+
# Then
178+
command = ["git", "remote", "remove", name]
179+
mock_execute.assert_called_once_with(command, cwd=str(local_path))
180+
181+
assert response.code == 204

schema/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
},
101101
{
102102
"command": "git:push",
103-
"args": { "force": true }
103+
"args": { "advanced": true }
104104
},
105105
{
106106
"command": "git:pull"
@@ -113,7 +113,7 @@
113113
"command": "git:reset-to-remote"
114114
},
115115
{
116-
"command": "git:add-remote"
116+
"command": "git:manage-remote"
117117
},
118118
{
119119
"command": "git:terminal-command"

0 commit comments

Comments
 (0)