Skip to content

Commit ba6412e

Browse files
fcollonvalmeeseeksmachine
authored andcommitted
Backport PR #691: FIx pushing when there are slashes in branch names or in remote name
1 parent 791db6a commit ba6412e

File tree

4 files changed

+118
-74
lines changed

4 files changed

+118
-74
lines changed

jupyterlab_git/git.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -890,12 +890,12 @@ async def get_current_branch(self, current_path):
890890
to the `branch` command to get the name.
891891
See https://git-blame.blogspot.com/2013/06/checking-current-branch-programatically.html
892892
"""
893-
command = ["git", "symbolic-ref", "HEAD"]
893+
command = ["git", "symbolic-ref", "--short", "HEAD"]
894894
code, output, error = await execute(
895895
command, cwd=os.path.join(self.root_dir, current_path)
896896
)
897897
if code == 0:
898-
return output.split("/")[-1].strip()
898+
return output.strip()
899899
elif "not a symbolic ref" in error.lower():
900900
current_branch = await self._get_current_branch_detached(current_path)
901901
return current_branch
@@ -939,18 +939,22 @@ async def get_upstream_branch(self, current_path, branch_name):
939939
code, output, error = await execute(
940940
command, cwd=os.path.join(self.root_dir, current_path)
941941
)
942-
if code == 0:
943-
return output.strip()
944-
elif "fatal: no upstream configured for branch" in error.lower():
945-
return None
946-
elif "unknown revision or path not in the working tree" in error.lower():
947-
return None
948-
else:
949-
raise Exception(
950-
"Error [{}] occurred while executing [{}] command to get upstream branch.".format(
951-
error, " ".join(command)
952-
)
953-
)
942+
if code != 0:
943+
return {"code": code, "command": " ".join(command), "message": error}
944+
rev_parse_output = output.strip()
945+
946+
command = ["git", "config", "--local", "branch.{}.remote".format(branch_name)]
947+
code, output, error = await execute(
948+
command, cwd=os.path.join(self.root_dir, current_path)
949+
)
950+
if code != 0:
951+
return {"code": code, "command": " ".join(cmd), "message": error}
952+
953+
remote_name = output.strip()
954+
remote_branch = rev_parse_output.strip().lstrip(remote_name+"/")
955+
return {"code": code, "remote_short_name": remote_name, "remote_branch": remote_branch}
956+
957+
954958

955959
async def _get_tag(self, current_path, commit_sha):
956960
"""Execute 'git describe commit_sha' to get

jupyterlab_git/handlers.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,10 @@ async def post(self):
401401
"""
402402
current_path = self.get_json_body()["current_path"]
403403
current_branch = await self.git.get_current_branch(current_path)
404-
upstream = await self.git.get_upstream_branch(current_path, current_branch)
405-
self.finish(json.dumps({"upstream": upstream}))
404+
response = await self.git.get_upstream_branch(current_path, current_branch)
405+
if response['code'] != 0:
406+
self.set_status(500)
407+
self.finish(json.dumps(response))
406408

407409

408410
class GitPullHandler(GitHandler):
@@ -437,32 +439,28 @@ async def post(self):
437439
current_path = data["current_path"]
438440

439441
current_local_branch = await self.git.get_current_branch(current_path)
440-
current_upstream_branch = await self.git.get_upstream_branch(
442+
upstream = await self.git.get_upstream_branch(
441443
current_path, current_local_branch
442444
)
443445

444-
if current_upstream_branch and current_upstream_branch.strip():
445-
upstream = current_upstream_branch.split("/")
446-
if len(upstream) == 1:
447-
# If upstream is a local branch
448-
remote = "."
449-
branch = ":".join(["HEAD", upstream[0]])
450-
else:
451-
# If upstream is a remote branch
452-
remote = upstream[0]
453-
branch = ":".join(["HEAD", upstream[1]])
454-
446+
if upstream['code'] == 0:
447+
branch = ":".join(["HEAD", upstream['remote_branch']])
455448
response = await self.git.push(
456-
remote, branch, current_path, data.get("auth", None)
449+
upstream['remote_short_name'], branch, current_path, data.get("auth", None)
457450
)
458451

459452
else:
460-
response = {
461-
"code": 128,
462-
"message": "fatal: The current branch {} has no upstream branch.".format(
463-
current_local_branch
464-
),
465-
}
453+
if ("no upstream configured for branch" in upstream['message'].lower()
454+
or 'unknown revision or path' in upstream['message'].lower()):
455+
response = {
456+
"code": 128,
457+
"message": "fatal: The current branch {} has no upstream branch.".format(
458+
current_local_branch
459+
),
460+
}
461+
else:
462+
self.set_status(500)
463+
466464
self.finish(json.dumps(response))
467465

468466

jupyterlab_git/tests/test_branch.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async def test_get_current_branch_success():
4444

4545
# Then
4646
mock_execute.assert_called_once_with(
47-
["git", "symbolic-ref", "HEAD"], cwd=os.path.join("/bin", "test_curr_path")
47+
["git", "symbolic-ref", "--short", "HEAD"], cwd=os.path.join("/bin", "test_curr_path")
4848
)
4949
assert "feature-foo" == actual_response
5050

@@ -337,11 +337,11 @@ async def test_get_current_branch_failure():
337337

338338
# Then
339339
mock_execute.assert_called_once_with(
340-
["git", "symbolic-ref", "HEAD"], cwd=os.path.join("/bin", "test_curr_path")
340+
["git", "symbolic-ref", "--short", "HEAD"], cwd=os.path.join("/bin", "test_curr_path")
341341
)
342342
assert (
343343
"Error [fatal: Not a git repository (or any of the parent directories): .git] "
344-
"occurred while executing [git symbolic-ref HEAD] command to get current branch."
344+
"occurred while executing [git symbolic-ref --short HEAD] command to get current branch."
345345
== str(error.value)
346346
)
347347

@@ -403,29 +403,42 @@ async def test_get_current_branch_detached_failure():
403403

404404
@pytest.mark.asyncio
405405
@pytest.mark.parametrize(
406-
"branch,upstream",
406+
"branch,upstream,remotename",
407407
[
408-
("feature-foo", "origin/master"),
409-
("master", "origin/master"),
410-
("feature-bar", "feature-foo"),
408+
("feature-foo", "master", "origin/withslash"),
409+
("master", "master", "origin"),
410+
("feature/bar", "feature-foo", ""),
411411
],
412412
)
413-
async def test_get_upstream_branch_success(branch, upstream):
413+
async def test_get_upstream_branch_success(branch, upstream, remotename):
414414
with patch("jupyterlab_git.git.execute") as mock_execute:
415415
# Given
416-
mock_execute.return_value = maybe_future((0, upstream, ""))
416+
mock_execute.side_effect = [
417+
maybe_future((0, remotename + '/' + upstream, '')),
418+
maybe_future((0, remotename, ''))
419+
]
417420

418421
# When
419422
actual_response = await Git(FakeContentManager("/bin")).get_upstream_branch(
420423
current_path="test_curr_path", branch_name=branch
421424
)
422425

423426
# Then
424-
mock_execute.assert_called_once_with(
425-
["git", "rev-parse", "--abbrev-ref", "{}@{{upstream}}".format(branch)],
426-
cwd=os.path.join("/bin", "test_curr_path"),
427+
mock_execute.assert_has_calls(
428+
[
429+
call(
430+
["git", "rev-parse", "--abbrev-ref", "{}@{{upstream}}".format(branch)],
431+
cwd=os.path.join("/bin", "test_curr_path"),
432+
),
433+
call(
434+
['git', 'config', '--local', 'branch.{}.remote'.format(branch)],
435+
cwd='/bin/test_curr_path',
436+
),
437+
438+
],
439+
any_order=False,
427440
)
428-
assert upstream == actual_response
441+
assert {'code': 0, 'remote_branch': upstream, 'remote_short_name': remotename} == actual_response
429442

430443

431444
@pytest.mark.asyncio
@@ -454,17 +467,12 @@ async def test_get_upstream_branch_failure(outputs, message):
454467
mock_execute.return_value = maybe_future(outputs)
455468

456469
# When
457-
if message:
458-
with pytest.raises(Exception) as error:
459-
await Git(FakeContentManager("/bin")).get_upstream_branch(
460-
current_path="test_curr_path", branch_name="blah"
461-
)
462-
assert message == str(error.value)
463-
else:
464-
response = await Git(FakeContentManager("/bin")).get_upstream_branch(
465-
current_path="test_curr_path", branch_name="blah"
466-
)
467-
assert response is None
470+
response = await Git(FakeContentManager("/bin")).get_upstream_branch(
471+
current_path="test_curr_path", branch_name="blah"
472+
)
473+
expected = {'code': 128, 'command': 'git rev-parse --abbrev-ref blah@{upstream}', 'message': outputs[2]}
474+
475+
assert response == expected
468476

469477
# Then
470478
mock_execute.assert_has_calls(
@@ -807,7 +815,7 @@ async def test_branch_success_detached_head():
807815
),
808816
# call to get current branch
809817
call(
810-
["git", "symbolic-ref", "HEAD"],
818+
["git", "symbolic-ref", "--short", "HEAD"],
811819
cwd=os.path.join("/bin", "test_curr_path"),
812820
),
813821
# call to get current branch name given a detached head

jupyterlab_git/tests/test_handlers.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,13 @@ class TestPush(ServerTest):
166166
@patch("jupyterlab_git.handlers.GitPushHandler.git", spec=Git)
167167
def test_push_handler_localbranch(self, mock_git):
168168
# Given
169-
mock_git.get_current_branch.return_value = maybe_future("foo")
169+
mock_git.get_current_branch.return_value = maybe_future("localbranch")
170170
mock_git.get_upstream_branch.return_value = maybe_future(
171-
"localbranch"
171+
{
172+
"code": 0,
173+
"remote_short_name": ".",
174+
"remote_branch": "localbranch"
175+
}
172176
)
173177
mock_git.push.return_value = maybe_future({"code": 0})
174178

@@ -178,7 +182,7 @@ def test_push_handler_localbranch(self, mock_git):
178182

179183
# Then
180184
mock_git.get_current_branch.assert_called_with("test_path")
181-
mock_git.get_upstream_branch.assert_called_with("test_path", "foo")
185+
mock_git.get_upstream_branch.assert_called_with("test_path", "localbranch")
182186
mock_git.push.assert_called_with(".", "HEAD:localbranch", "test_path", None)
183187

184188
assert response.status_code == 200
@@ -188,10 +192,11 @@ def test_push_handler_localbranch(self, mock_git):
188192
@patch("jupyterlab_git.handlers.GitPushHandler.git", spec=Git)
189193
def test_push_handler_remotebranch(self, mock_git):
190194
# Given
191-
mock_git.get_current_branch.return_value = maybe_future("foo")
192-
mock_git.get_upstream_branch.return_value = maybe_future(
193-
"origin/remotebranch"
194-
)
195+
mock_git.get_current_branch.return_value = maybe_future("foo/bar")
196+
upstream = {"code": 0,
197+
"remote_short_name": "origin/something",
198+
"remote_branch": "remote-branch-name"}
199+
mock_git.get_upstream_branch.return_value = maybe_future(upstream)
195200
mock_git.push.return_value = maybe_future({"code": 0})
196201

197202
# When
@@ -200,9 +205,9 @@ def test_push_handler_remotebranch(self, mock_git):
200205

201206
# Then
202207
mock_git.get_current_branch.assert_called_with("test_path")
203-
mock_git.get_upstream_branch.assert_called_with("test_path", "foo")
208+
mock_git.get_upstream_branch.assert_called_with("test_path", "foo/bar")
204209
mock_git.push.assert_called_with(
205-
"origin", "HEAD:remotebranch", "test_path", None
210+
"origin/something", "HEAD:remote-branch-name", "test_path", None
206211
)
207212

208213
assert response.status_code == 200
@@ -213,7 +218,12 @@ def test_push_handler_remotebranch(self, mock_git):
213218
def test_push_handler_noupstream(self, mock_git):
214219
# Given
215220
mock_git.get_current_branch.return_value = maybe_future("foo")
216-
mock_git.get_upstream_branch.return_value = maybe_future("")
221+
upstream = {
222+
"code": 128,
223+
"command": "",
224+
"message": "fatal: no upstream configured for branch 'foo'"
225+
}
226+
mock_git.get_upstream_branch.return_value = maybe_future(upstream)
217227
mock_git.push.return_value = maybe_future({"code": 0})
218228

219229
# When
@@ -234,23 +244,47 @@ def test_push_handler_noupstream(self, mock_git):
234244

235245

236246
class TestUpstream(ServerTest):
247+
@patch("jupyterlab_git.handlers.GitUpstreamHandler.git", spec=Git)
248+
def test_upstream_handler_forward_slashes(self, mock_git):
249+
# Given
250+
mock_git.get_current_branch.return_value = maybe_future("foo/bar")
251+
upstream = {"code": 0,
252+
"remote_short_name": "origin/something",
253+
"remote_branch": "foo/bar"}
254+
mock_git.get_upstream_branch.return_value = maybe_future(upstream)
255+
256+
# When
257+
body = {"current_path": "test_path"}
258+
response = self.tester.post(["upstream"], body=body)
259+
260+
# Then
261+
mock_git.get_current_branch.assert_called_with("test_path")
262+
mock_git.get_upstream_branch.assert_called_with("test_path", "foo/bar")
263+
264+
assert response.status_code == 200
265+
payload = response.json()
266+
assert payload == upstream
267+
237268
@patch("jupyterlab_git.handlers.GitUpstreamHandler.git", spec=Git)
238269
def test_upstream_handler_localbranch(self, mock_git):
239270
# Given
240-
mock_git.get_current_branch.return_value = maybe_future("foo")
241-
mock_git.get_upstream_branch.return_value = maybe_future("bar")
271+
mock_git.get_current_branch.return_value = maybe_future("foo/bar")
272+
upstream = {"code": 0,
273+
"remote_short_name": ".",
274+
"remote_branch": "foo/bar"}
275+
mock_git.get_upstream_branch.return_value = maybe_future(upstream)
242276

243277
# When
244278
body = {"current_path": "test_path"}
245279
response = self.tester.post(["upstream"], body=body)
246280

247281
# Then
248282
mock_git.get_current_branch.assert_called_with("test_path")
249-
mock_git.get_upstream_branch.assert_called_with("test_path", "foo")
283+
mock_git.get_upstream_branch.assert_called_with("test_path", "foo/bar")
250284

251285
assert response.status_code == 200
252286
payload = response.json()
253-
assert payload == {"upstream": "bar"}
287+
assert payload == upstream
254288

255289

256290
class TestDiffContent(ServerTest):

0 commit comments

Comments
 (0)