blob: 5d26cca88ca23c92d4a0aa5689cbcec444273093 [file] [log] [blame]
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Use with an IDE to copy a link to the current file/line to your clipboard.
Public and private repos are supported. The internal/public arguments are only
used if the repo is public, private repos always link to the internal code
search.
By default always generates a link to the public code search, but can also
generate links to the internal code search.
Currently only works locally and if using X11.
To allow use over SSH, enable X11 forwarding.
Client side: -X, or add "X11Forwarding yes" to your ~/.ssh/config.
Server side: "X11Forwarding yes" must be in /etc/ssh/sshd_config.
IDE integration instructions. Please add your IDE if it's not listed, or update
the instructions if they're vague or out of date!
For Intellij:
Create a new External Tool (Settings > Tools > External Tools).
Tool Settings:
Program: ~/chromiumos/chromite/contrib/generate_cs_path
Arguments: $FilePath$ -l $LineNumber$
For VSCode:
Create a custom task (code.visualstudio.com/docs/editor/tasks#_custom-tasks)
with the command:
~/chromiumos/chromite/contrib/generate_cs_path ${file} -l ${lineNumber}
"""
import os
from pathlib import Path
import sys
from chromite.lib import commandline
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import git
class CodeSearch(object):
"""format returns a url to the code specified"""
@classmethod
def format(cls, attrs, opts, checkout_path, relative_path):
raise NotImplementedError()
class Gitiles(CodeSearch):
"""format returns a url to the code specified"""
@classmethod
def format(cls, attrs, opts, checkout_path, relative_path):
line = f"#{opts.line}" if opts.line else ""
return f'{attrs["push_url"]}/+/{attrs["sha"]}/{relative_path}{line}'
class PublicCS(CodeSearch):
"""format returns a url to the code specified"""
@classmethod
def format(cls, attrs, opts, checkout_path, relative_path):
line = f";l={opts.line}" if opts.line else ""
sha = attrs["sha"] if opts.upstream_sha else "HEAD"
# HEAD is the ref for the codesearch repo, not the project itself.
return (
"https://source.chromium.org/chromiumos/chromiumos/codesearch/+/"
+ f"{sha}:{checkout_path}/{relative_path}{line}"
)
class InternalCS(CodeSearch):
"""format returns a url to the code specified"""
@classmethod
def format(cls, attrs, opts, checkout_path, relative_path):
line = f";l={opts.line}" if opts.line else ""
sha = f';rcl={attrs["sha"]}' if opts.upstream_sha else ""
return (
"http://cs/chromeos_public/"
+ f"{checkout_path}/{relative_path}{line}{sha}"
)
class PrivateCS(CodeSearch):
"""format returns a url to the code specified"""
@classmethod
def format(cls, attrs, opts, checkout_path, relative_path):
line = f";l={opts.line}" if opts.line else ""
sha = f';rcl={attrs["sha"]}' if opts.upstream_sha else ""
return (
"http://cs/chromeos_internal/"
+ f"{checkout_path}/{relative_path}{line}{sha}"
)
def GetParser():
"""Build the argument parser."""
parser = commandline.ArgumentParser(description=__doc__)
# Public/internal code search selection arguments.
type_group = parser.add_mutually_exclusive_group()
type_group.add_argument(
"-p",
"--public",
dest="public_link",
action="store_true",
default=True,
help="Generate a link to the public code search.",
)
type_group.add_argument(
"-i",
"--internal",
dest="public_link",
action="store_false",
help="Generate a link to the internal code search.",
)
type_group.add_argument(
"-g",
"--gitiles",
action="store_true",
default=False,
help="Generate a gitiles link rather than a code search link.",
)
parser.add_argument("-l", "--line", type=int, help="Line number.")
parser.add_argument(
"--upstream-sha", action="store_true", help="Link to the upstream sha."
)
action_group = parser.add_mutually_exclusive_group()
action_group.add_argument(
"-o",
"--open",
action="store_true",
default=False,
help="Open the link in a browser rather than copying it to the "
"clipboard.",
)
action_group.add_argument(
"-s",
"--show",
action="store_true",
default=False,
help="Output the link to stdout rather than copying it to the"
"clipboard.",
)
parser.add_argument("path", type="path", help="Path to a file.")
return parser
def ParseArguments(argv):
"""Parse and validate arguments."""
parser = GetParser()
opts = parser.parse_args(argv)
opts.path = Path(opts.path).relative_to(constants.SOURCE_ROOT)
opts.Freeze()
return opts
def GenerateLink(attrs, opts, checkout_path, relative_path):
"""Generate link to CS based on on parsed arguments and checkout."""
if opts.gitiles:
base = Gitiles
elif attrs.get("remote_alias") == "cros-internal":
# Private repos not on public CS, so force internal private.
base = PrivateCS
elif opts.public_link:
base = PublicCS
else:
base = InternalCS
return base.format(attrs, opts, checkout_path, relative_path)
def main(argv):
opts = ParseArguments(argv)
checkout = git.ManifestCheckout.Cached(opts.path)
# Find the project.
checkout_path = None
attrs = {}
for checkout_path, attrs in checkout.checkouts_by_path.items():
try:
relative_path = opts.path.relative_to(checkout_path)
break
except ValueError:
continue
else:
cros_build_lib.Die("No project found for %s.", opts.path)
if opts.upstream_sha:
attrs["sha"] = git.RunGit(
attrs["local_path"], ["rev-parse", attrs["tracking_branch"]]
).stdout.strip()
else:
attrs["sha"] = git.RunGit(
attrs["local_path"], ["rev-parse", "HEAD"]
).stdout.strip()
final_link = GenerateLink(attrs, opts, checkout_path, relative_path)
is_mac_os = sys.platform.startswith("darwin")
if opts.open:
cmd = ["open" if is_mac_os else "xdg-open", final_link]
os.execvp(cmd[0], cmd)
elif opts.show:
print(final_link)
else:
cmd = ["pbcopy"] if is_mac_os else ["xsel", "--clipboard", "--input"]
cros_build_lib.run(cmd, input=final_link)