Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Push Remote Branch Based on Git Config #412

Closed
wants to merge 8 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use default unique remote if it exists
fcollonval committed May 26, 2020

Verified

This commit was signed with the committer’s verified signature. The key has expired.
addaleax Anna Henningsen
commit cce05067db05b4e77b6b20724b2d21b12200efb0
47 changes: 35 additions & 12 deletions jupyterlab_git/git.py
Original file line number Diff line number Diff line change
@@ -832,10 +832,14 @@ async def pull(self, curr_fb_path, auth=None, cancel_on_conflict=False):

return response

async def push(self, remote, branch, curr_fb_path, auth=None):
async def push(self, remote, branch, curr_fb_path, auth=None, set_upstream=False):
"""
Execute `git push $UPSTREAM $BRANCH`. The choice of upstream and branch is up to the caller.
"""
command = ["git", "push"]
if set_upstream:
command.append("--set-upstream")
command.extend([remote, branch])
env = os.environ.copy()
if auth:
env["GIT_TERMINAL_PROMPT"] = "1"
@@ -1077,7 +1081,7 @@ async def _is_binary(self, filename, ref, top_repo_path):
# For binary files, `--numstat` outputs two `-` characters separated by TABs:
return output.startswith('-\t-\t')

def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
async def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
"""Handle call to `git remote add` command.
top_repo_path: str
@@ -1088,16 +1092,35 @@ def remote_add(self, top_repo_path, url, name=DEFAULT_REMOTE_NAME):
Remote name; default "origin"
"""
cmd = ["git", "remote", "add", name, url]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=top_repo_path)
_, my_error = p.communicate()
if p.returncode == 0:
return {
"code": p.returncode,
code, _, error = await execute(cmd, cwd=top_repo_path)
response = {
"code": code,
"command": " ".join(cmd)
}
else:
return {
"code": p.returncode,
"command": " ".join(cmd),
"message": my_error.decode("utf-8").strip()
if code != 0:
response["message"] = error

return response

async def remote_show(self, path):
"""Handle call to `git remote show` command.
Args:
path (str): Git repository path
Returns:
List[str]: Known remotes
"""
command = ["git", "remote", "show"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to instead use git remote -v show here? As some future proofing of this function in case it is ever used to determine URLs or do something with determining fetch vs push urls?
For me git remote show:

origin
upstream

vs git remote -v show:

origin	[email protected]:ianhi/jupyterlab-git.git (fetch)
origin	[email protected]:ianhi/jupyterlab-git.git (push)
upstream	[email protected]:jupyterlab/jupyterlab-git.git (fetch)
upstream	[email protected]:jupyterlab/jupyterlab-git.git (push)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I'll stick to current behavior as there is no feature requiring more information.

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()]
else:
response["message"] = error

return response

61 changes: 49 additions & 12 deletions jupyterlab_git/handlers.py
Original file line number Diff line number Diff line change
@@ -268,13 +268,13 @@ async def post(self):
class GitRemoteAddHandler(GitHandler):
"""Handler for 'git remote add <name> <url>'."""

def post(self):
async def post(self):
"""POST request handler to add a remote."""
data = self.get_json_body()
top_repo_path = data["top_repo_path"]
name = data.get("name", DEFAULT_REMOTE_NAME)
url = data["url"]
output = self.git.remote_add(top_repo_path, url, name)
output = await self.git.remote_add(top_repo_path, url, name)
if(output["code"] == 0):
self.set_status(201)
else:
@@ -437,16 +437,33 @@ async def post(self):
"""
POST request handler,
pushes committed files from your current branch to a remote branch
Request body:
{
current_path: string, # Git repository path
remote?: string # Remote to push to; i.e. <remote_name> or <remote_name>/<branch>
}
"""
data = self.get_json_body()
current_path = data["current_path"]
known_remote = data.get("remote")

current_local_branch = await self.git.get_current_branch(current_path)

set_upstream = False
current_upstream_branch = await self.git.get_upstream_branch(
current_path, current_local_branch
)
has_upstream = current_upstream_branch and current_upstream_branch.strip()
if known_remote is not None:
if "/" in known_remote:
current_upstream_branch = known_remote
else:
current_upstream_branch = "{}/{}".format(known_remote, current_local_branch)
set_upstream = not has_upstream
has_upstream = True

if current_upstream_branch and current_upstream_branch.strip():
if has_upstream:
upstream = current_upstream_branch.split("/")
if len(upstream) == 1:
# If upstream is a local branch
@@ -458,25 +475,45 @@ async def post(self):
branch = ":".join(["HEAD", upstream[1]])

response = await self.git.push(
remote, branch, current_path, data.get("auth", None)
remote, branch, current_path, data.get("auth", None), set_upstream
)

else:
# Allow users to specify upstream through their configuration
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-pushdefault
config_options = self.git.config(current_path)["options"]
default_remote = config_options.get('remote.pushdefault')
default_remote_branch = config_options.get('push.default') == 'current'

if default_remote_branch and default_remote:
response = self.git.push(default_remote, current_local_branch, current_path)
# Or use the remote defined if only one remote exists
config = await self.git.config(current_path)
config_options = config["options"]
list_remotes = await self.git.remote_show(current_path)
remotes = list_remotes.get("remotes", list())
push_default = config_options.get('remote.pushdefault')

default_remote = None
if push_default is not None and push_default in remotes:
default_remote = push_default
elif len(remotes) == 1:
default_remote = remotes[0]

if default_remote is not None:
response = await self.git.push(
default_remote,
current_local_branch,
current_path,
data.get("auth", None),
set_upstream=True,
)
else:
response = {
"code": 128,
"message": "fatal: The current branch {} has no upstream branch.".format(
current_local_branch
),
"remotes": remotes # Returns the list of known remotes
}

if response["code"] != 0:
self.set_status(500)

self.finish(json.dumps(response))


@@ -520,9 +557,9 @@ async def post(self):
top_repo_path = data["path"]
options = data.get("options", {})
filtered_options = {k: v for k, v in options.items() if k in ALLOWED_OPTIONS}
response = await self.git.config(top_repo_path, **options)
response = await self.git.config(top_repo_path, **filtered_options)
if "options" in response:
response["options"] = {k:v for k, v in response["options"] if k in ALLOWED_OPTIONS}
response["options"] = {k:v for k, v in response["options"].items() if k in ALLOWED_OPTIONS}

if response["code"] != 0:
self.set_status(500)
2 changes: 1 addition & 1 deletion jupyterlab_git/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ def test_git_get_config_multiline(self, mock_execute):

@patch("jupyterlab_git.git.execute")
@patch(
"jupyterlab_git.git.ALLOWED_OPTIONS",
"jupyterlab_git.handlers.ALLOWED_OPTIONS",
["alias.summary", "alias.topic-base-branch-name"],
)
def test_git_get_config_accepted_multiline(self, mock_execute):
Loading