1
0
Files
nixfiles/pkgs/git-show-link/git-show-link.py

178 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
from dataclasses import dataclass
import re
import subprocess
from pathlib import Path
REMOTE_TYPES = {
"github": {
"match": re.compile(r'git@github.com:(?P<username>[\w\.-]+)/(?P<project>[\w\.-]+).git'),
"format-branch": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.branch}/",
"format-branch-file": lambda g: f"https://github.com/{g.username}/{g.project}/blob/{g.branch}/{g.file}",
"format-branch-dir": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.branch}/{g.dir}",
"format-commit": lambda g: f"https://github.com/{g.username}/{g.project}/commit/{g.commit}/",
"format-commit-file": lambda g: f"https://github.com/{g.username}/{g.project}/blob/{g.commit}/{g.file}",
"format-commit-dir": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.commit}/{g.dir}",
},
"gitea": {
"match": re.compile(r'(?P<gituser>[\w\.-]+)@(?P<host>[\w\.-]+):(?P<username>[\w\.-]+)/(?P<project>[\w\.-]+).git'),
"format-branch": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/",
"format-branch-file": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/{g.file}",
"format-branch-dir": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/{g.dir}",
"format-commit": lambda g: f"https://{g.host}/{g.username}/{g.project}/commit/{g.commit}/",
"format-commit-file": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/commit/{g.commit}/{g.file}",
"format-commit-dir": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/commit/{g.commit}/{g.dir}",
},
}
@dataclass
class FormatArgs:
gituser: str = None
host: str = None
username: str = None
project: str = None
commit: str = None
branch: str = None
file: str = None
dir: str = None
def is_git_repo():
s = subprocess.run(["git", "rev-parse"], capture_output=True, text=True)
return s.returncode == 0
def get_git_dir():
s = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True)
return Path(s.stdout.strip())
def get_remote_branch():
s = subprocess.run(["git", "status", "--porcelain", "-uno", "-b", "--no-ahead-behind"], capture_output=True, text=True)
if s.stdout.startswith("## HEAD (no branch)"):
print("Detached head, can't link")
exit(1)
git_status_branch_info = s.stdout.splitlines()[0][3:].split()[0]
branches = git_status_branch_info.split("...")
if len(branches) != 2:
raise Exception("no branch name found")
local_branch, remote_branch = branches
remote, branch = remote_branch.split("/", maxsplit=1)
return {
"remote": remote,
"branch": branch,
}
def get_remote_url(remote):
s = subprocess.run(["git", "remote", "get-url", remote], capture_output=True, text=True)
remote_url = s.stdout.strip()
return remote_url
def get_last_commit():
s = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True)
commit = s.stdout.strip()
return commit
def main():
parser = argparse.ArgumentParser(
prog='git-show-link',
)
parser.add_argument("path", nargs="?", default=None, help="Path to link to specific file or directory")
parser.add_argument("--branch", dest="display_branch", action='store_true', help="Display link to branch, instead to commit")
parser.add_argument("--remote-type", dest="remote_type", choices=REMOTE_TYPES.keys(), help="Specify remote type")
args = parser.parse_args()
if not is_git_repo():
print("Not a git repo")
exit(1)
git_dir_path = get_git_dir()
r = get_remote_branch()
remote_url = get_remote_url(r["remote"])
selected_remote_types = REMOTE_TYPES
if args.remote_type is not None:
selected_remote_types = {
args.remote_type: REMOTE_TYPES[args.remote_type],
}
remote_type_found = False
for remote_type_name, remote_type in selected_remote_types.items():
m = remote_type["match"].match(remote_url)
if m is None:
continue
remote_type_found = True
g = FormatArgs(**m.groupdict())
if args.path is not None:
path = Path(args.path).absolute()
path = path.relative_to(git_dir_path)
if path.is_dir():
path = str(path)
if path == ".":
path = ""
else:
path += "/"
g.dir = path
else:
g.file = str(path)
if g.file is not None:
if args.display_branch:
g.branch = r["branch"]
print(remote_type["format-branch-file"](g))
else:
commit = get_last_commit()
g.commit = commit
print(remote_type["format-commit-file"](g))
elif g.dir is not None:
if args.display_branch:
g.branch = r["branch"]
print(remote_type["format-branch-dir"](g))
else:
commit = get_last_commit()
g.commit = commit
print(remote_type["format-commit-dir"](g))
else:
if args.display_branch:
g.branch = r["branch"]
print(remote_type["format-branch"](g))
else:
commit = get_last_commit()
g.commit = commit
print(remote_type["format-commit"](g))
break
if not remote_type_found:
print("No remote type matched")
exit(1)
if __name__ == "__main__":
main()