mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 07:17:55 +03:00
link subcommand for github extension
Summary: With the `github` extension enabled, `hg link` associates pull request data with a commit in the metalog. Note that nothing verifies that this linkage exists on GitHub. Ultimately, `hg submit` will be required to remap the commits within the actual pull request on GitHub. I'm not sure what best practices are in terms of using flags versus positional arguments in the CLI, but we can continue to experiment with that, for now. Reviewed By: quark-zju Differential Revision: D35638199 fbshipit-source-id: f72514b13627a8ef845ffb99e6ae3c86098061cd
This commit is contained in:
parent
f9290fd6d5
commit
13312961a1
@ -9,7 +9,7 @@
|
||||
from edenscm.mercurial import registrar
|
||||
from edenscm.mercurial.i18n import _
|
||||
|
||||
from . import submit
|
||||
from . import link, submit
|
||||
|
||||
cmdtable = {}
|
||||
command = registrar.command(cmdtable)
|
||||
@ -30,3 +30,21 @@ command = registrar.command(cmdtable)
|
||||
def submit_cmd(ui, repo, *args, **opts):
|
||||
"""create or update GitHub pull requests from local commits"""
|
||||
return submit.submit(ui, repo, *args, **opts)
|
||||
|
||||
|
||||
@command(
|
||||
"link",
|
||||
[("r", "rev", "", _("revision to link"), _("REV"))],
|
||||
_("[-r REV] PULL_REQUEST"),
|
||||
)
|
||||
def link_cmd(ui, repo, *args, **opts):
|
||||
"""indentify a commit as the head of a GitHub pull request
|
||||
|
||||
A PULL_REQUEST can be specified in a number of formats:
|
||||
|
||||
- GitHub URL to the PR: https://github.com/facebook/react/pull/42
|
||||
|
||||
- Integer: Number for the PR. Uses 'paths.upstream' as the target repo,
|
||||
if specified; otherwise, falls back to 'paths.default'.
|
||||
"""
|
||||
return link.link(ui, repo, *args, **opts)
|
||||
|
97
eden/scm/edenscm/hgext/github/link.py
Normal file
97
eden/scm/edenscm/hgext/github/link.py
Normal file
@ -0,0 +1,97 @@
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This software may be used and distributed according to the terms of the
|
||||
# GNU General Public License version 2.
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from edenscm.mercurial import error, scmutil
|
||||
from edenscm.mercurial.i18n import _
|
||||
|
||||
from .pullrequeststore import PullRequest, PullRequestStore
|
||||
|
||||
|
||||
def link(ui, repo, *args, **opts):
|
||||
if len(args) != 1:
|
||||
raise error.Abort(_("must specify a pull request"))
|
||||
|
||||
pr_arg = args[0]
|
||||
pull_request = resolve_pr_arg(pr_arg, ui)
|
||||
if not pull_request:
|
||||
raise error.Abort(_("could not resolve pull request: '%%s'") % pr_arg)
|
||||
|
||||
ctx = scmutil.revsingle(repo, opts.get("rev"), None)
|
||||
pr_store = PullRequestStore(repo)
|
||||
pr_store.map_commit_to_pull_request(ctx.node(), pull_request)
|
||||
|
||||
|
||||
def resolve_pr_arg(pr_arg: str, ui) -> Optional[PullRequest]:
|
||||
num = try_parse_int(pr_arg)
|
||||
if num:
|
||||
upstream = try_find_upstream(ui)
|
||||
if upstream:
|
||||
return try_parse_pull_request_url(f"{upstream}/pull/{num}")
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return try_parse_pull_request_url(pr_arg)
|
||||
|
||||
|
||||
def try_parse_int(s: str) -> Optional[int]:
|
||||
"""tries to parse s as a positive integer"""
|
||||
pattern = r"^[1-9][0-9]+$"
|
||||
match = re.match(pattern, s)
|
||||
return int(match[0]) if match else None
|
||||
|
||||
|
||||
def try_parse_pull_request_url(url: str) -> Optional[PullRequest]:
|
||||
"""parses the url into a PullRequest if it is in the expected format"""
|
||||
pattern = r"^https://github.com/([^/]+)/([^/]+)/pull/([1-9][0-9]+)$"
|
||||
match = re.match(pattern, url)
|
||||
if match:
|
||||
pull_request = PullRequest()
|
||||
pull_request.owner = match[1]
|
||||
pull_request.name = match[2]
|
||||
pull_request.number = int(match[3])
|
||||
return pull_request
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def try_find_upstream(ui) -> Optional[str]:
|
||||
"""checks [paths] in .hgrc for an upstream GitHub repo"""
|
||||
for remote in ["upstream", "default"]:
|
||||
url = ui.config("paths", "upstream")
|
||||
if url:
|
||||
repo_url = normalize_github_repo_url(url)
|
||||
if repo_url:
|
||||
return repo_url
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def normalize_github_repo_url(url: str) -> Optional[str]:
|
||||
"""parses the following URL formats:
|
||||
|
||||
https://github.com/bolinfest/escoria-demo-game
|
||||
https://github.com/bolinfest/escoria-demo-game.git
|
||||
git@github.com:bolinfest/escoria-demo-game.git
|
||||
|
||||
and returns:
|
||||
|
||||
https://github.com/bolinfest/escoria-demo-game
|
||||
|
||||
which is suitable for constructing URLs to pull requests.
|
||||
"""
|
||||
https_pattern = r"^https://github.com/([^/]+)/([^/]+?)(?:\.git)?$"
|
||||
https_match = re.match(https_pattern, url)
|
||||
if https_match:
|
||||
return f"https://github.com/{https_match[1]}/{https_match[2]}"
|
||||
|
||||
ssh_pattern = r"^git@github.com:([^/]+)/([^/]+?)(?:\.git)?$"
|
||||
ssh_match = re.match(ssh_pattern, url)
|
||||
if ssh_match:
|
||||
return f"https://github.com/{ssh_match[1]}/{ssh_match[2]}"
|
||||
|
||||
return None
|
Loading…
Reference in New Issue
Block a user