Skip to content
Snippets Groups Projects
Commit 64c7663e authored by nimrod's avatar nimrod
Browse files

Working git-manage (I think).

- Finished implementing all of the functionality I had in mind (for
  now).
- Added a bunch of info output.
- Fixed a few small issues.
- Worked around an issue with creating an initial commit in GitHub
  (ended up doing it with the git CLI).
- More information in the docstrings for the top level functions.
parent fc71121e
No related branches found
No related tags found
No related merge requests found
...@@ -9,6 +9,7 @@ import sys ...@@ -9,6 +9,7 @@ import sys
import github3.exceptions # pylint: disable=import-error import github3.exceptions # pylint: disable=import-error
import gitlab.exceptions # pylint: disable=import-error import gitlab.exceptions # pylint: disable=import-error
import passhole.passhole # pylint: disable=import-error import passhole.passhole # pylint: disable=import-error
import sh # pylint: disable=import-error
sys.path.append(os.path.expanduser("~/Documents/bin")) sys.path.append(os.path.expanduser("~/Documents/bin"))
...@@ -17,11 +18,12 @@ import rcfiles.github # noqa: E402 pylint: disable=wrong-import-position ...@@ -17,11 +18,12 @@ import rcfiles.github # noqa: E402 pylint: disable=wrong-import-position
import rcfiles.gitlab # noqa: E402 pylint: disable=wrong-import-position import rcfiles.gitlab # noqa: E402 pylint: disable=wrong-import-position
GH_MIRROR_PREFIX = "https://*****@github.com" GH_MIRROR_PREFIX = "https://*****@github.com/"
def github_mirrors(project, gh_conn): def github_mirrors(project, gh_conn):
"""Return a list of GitHub repositories that are mirrored from GitLab.""" """Return a list of GitHub repositories that are mirrored from the GitLab
project."""
repos = [] repos = []
for mirror in project.remote_mirrors.list(all=True): for mirror in project.remote_mirrors.list(all=True):
if mirror.url.startswith(GH_MIRROR_PREFIX): if mirror.url.startswith(GH_MIRROR_PREFIX):
...@@ -39,14 +41,22 @@ def github_mirrors(project, gh_conn): ...@@ -39,14 +41,22 @@ def github_mirrors(project, gh_conn):
def mirror_project(project, gh_conn, token): def mirror_project(project, gh_conn, token):
"""Mirror a project to GitHub.""" """Mirror a GitLab project to GitHub."""
gh_me = rcfiles.github.me(gh_conn) gh_me = rcfiles.github.me(gh_conn)
mirror_url = f"https://{token}@github.com/{gh_me}/{project.name}.git" mirror_url = f"https://{token}@github.com/{gh_me}/{project.name}.git"
try: try:
gh_repo = gh_conn.repository(project.name, gh_me) gh_repo = gh_conn.repository(gh_me, project.name)
print(
f"Using existing GitHub repository {gh_repo.html_url}.",
file=sys.stderr,
)
except github3.exceptions.NotFoundError: except github3.exceptions.NotFoundError:
gh_repo = gh_conn.create_repository(project.name) gh_repo = gh_conn.create_repository(project.name)
print(
f"Created a new GitHub reposiroty {gh_repo.html_url}.",
file=sys.stderr,
)
gh_repo.edit( gh_repo.edit(
project.name, project.name,
...@@ -54,7 +64,6 @@ def mirror_project(project, gh_conn, token): ...@@ -54,7 +64,6 @@ def mirror_project(project, gh_conn, token):
description=f"Mirror of {project.web_url}", description=f"Mirror of {project.web_url}",
has_issues=False, has_issues=False,
has_wiki=False, has_wiki=False,
default_branch=project.default_branch,
has_projects=False, has_projects=False,
) )
...@@ -85,7 +94,7 @@ def get_mirror_token(): ...@@ -85,7 +94,7 @@ def get_mirror_token():
# password. # password.
db = passhole.passhole.open_database() db = passhole.passhole.open_database()
entry = db.find_entries(path=ENTRY_PATH, first=True) entry = db.find_entries(path=ENTRY_PATH, first=True)
return passhole.passhole.get_field(entry, TOKEN_FIELD) return entry.get_custom_property(TOKEN_FIELD)
def guess_remote(remote_type="gitlab", gl_conn=None): def guess_remote(remote_type="gitlab", gl_conn=None):
...@@ -154,28 +163,155 @@ def guess_name(args, gh_conn=None, gl_conn=None): ...@@ -154,28 +163,155 @@ def guess_name(args, gh_conn=None, gl_conn=None):
name = rcfiles.github.url_to_name(remote["url"]) name = rcfiles.github.url_to_name(remote["url"])
else: else:
name = rcfiles.gitlab.url_to_name(gl_conn, remote["url"]) name = rcfiles.gitlab.url_to_name(gl_conn, remote["url"])
print(
f"""Name not provided, using {name} from the {remote["name"]} remote.""", # noqa: E501
file=sys.stderr,
)
else: else:
if "github" in args and args.github: if "github" in args and args.github:
if "/" in args.name: if "/" in args.name:
name = args.name name = args.name
else: else:
print(
"Name does not include project, defaulting to the GitHub user.", # noqa: E501
file=sys.stderr,
)
name = f"{rcfiles.github.me(gh_conn)}/{args.name}" name = f"{rcfiles.github.me(gh_conn)}/{args.name}"
else: else:
if "/" in args.name: if "/" in args.name:
name = args.name name = args.name
else: else:
print(
"Name does not include project, defaulting to the GitLab user.", # noqa: E501
file=sys.stderr,
)
name = f"{rcfiles.gitlab.me(gl_conn)}/{args.name}" name = f"{rcfiles.gitlab.me(gl_conn)}/{args.name}"
return name return name
def create_github_repo(args):
"""Create a new GitHub repository.
Does the following:
- Creates the mirror.
- Clones the repository.
- Commits an initial empty commit.
"""
if "/" in args.name:
parser.error("Can't specify an organization.")
if args.internal or args.private:
parser.error("Can't create internal or private GitHub repositories.")
try:
conn = rcfiles.github.connect()
except Exception as e: # pylint: disable=broad-except
parser.error(f"Failed to connect to GitHub: {e}")
repo = conn.create_repository(
args.name,
description=args.description,
)
print(
f"Created a new GitHub repository {repo.html_url}.",
file=sys.stderr,
)
rcfiles.git.git.clone(repo.ssh_url)
print("Cloned repository.", file=sys.stderr)
with sh.pushd(repo.name):
rcfiles.git.git.commit(
"--allow-empty", "--only", "--message", "Initial empty commit."
)
rcfiles.git.git.push("origin")
print(
"Committed an initial empty commit.",
file=sys.stderr,
)
def create_gitlab_repo(args):
"""Create a new GitLab repository.
Does the following:
- Creates the repository.
- Creates the mirror.
- Commits an initial empty commit.
- Clones the repository.
- Adds the mirror remote.
"""
if args.private and args.internal:
parser.error("Repository can be internal or private, not both.")
try:
conn = rcfiles.gitlab.connect()
except Exception as e: # pylint: disable=broad-except
parser.error(f"Failed to connect to GitLab: {e}")
if args.private:
visibility = "private"
elif args.internal:
visibility = "internal"
else:
visibility = "public"
name_with_namespace = guess_name(args, gl_conn=conn, gh_conn=None)
namespace, name = name_with_namespace.split("/")
if namespace == rcfiles.gitlab.me(conn):
project = conn.projects.create(
{
"name": name,
"description": args.description,
"visibility": visibility,
}
)
else:
group = conn.groups.get(namespace)
project = conn.projects.create(
{
"name": name,
"description": args.description,
"visibility": visibility,
"namespace_id": group.id,
}
)
print(
f"Created a new {visibility} GitLab repository {project.web_url}.",
file=sys.stderr,
)
if args.mirror:
gh_repo = mirror_repo(args)
rcfiles.gitlab.empty_commit(project)
print(
"Committed an initial empty commit.",
file=sys.stderr,
)
rcfiles.git.git.clone(project.ssh_url_to_repo)
print("Cloned repository.", file=sys.stderr)
if args.mirror:
rcfiles.git.add_remote(project.name, "github", gh_repo.ssh_url)
print("Added a remote for the mirror repository.", file=sys.stderr)
def create_repo(args): def create_repo(args):
"""Create a new repository.""" """Create a new repository."""
if args.mirror and args.github: if args.mirror and args.github:
parser.error("Can't mirror from GitHub to GitLab.") parser.error("Can't mirror from GitHub to GitLab.")
if args.github:
create_github_repo(args)
else:
create_gitlab_repo(args)
def mirror_repo(args): def mirror_repo(args):
"""Mirror a GitLab repository to GitHub.""" """Mirror a GitLab repository to GitHub.
Does the following:
- Creates the mirror.
- Adds the mirror remote.
"""
try: try:
gh_conn = rcfiles.github.connect() gh_conn = rcfiles.github.connect()
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
...@@ -193,12 +329,25 @@ def mirror_repo(args): ...@@ -193,12 +329,25 @@ def mirror_repo(args):
parser.error(f"Could not find GitLab project {name}.") parser.error(f"Could not find GitLab project {name}.")
gh_repo = mirror_project(project, gh_conn, get_mirror_token()) gh_repo = mirror_project(project, gh_conn, get_mirror_token())
print(
f"Setup mirror for {project.web_url} to {gh_repo.html_url}.",
file=sys.stderr,
)
if args.name is None and rcfiles.git.in_repo(): if args.name is None and rcfiles.git.in_repo():
rcfiles.git.add_remote(".", "github", gh_repo.ssh_url) rcfiles.git.add_remote(".", "github", gh_repo.ssh_url)
print("Added a remote for the mirror repository.", file=sys.stderr)
return gh_repo
def archive_repo(args): def archive_repo(args):
"""Archive a repository.""" """Archive a repository.
Does the following:
- Archives the repository (sets it to read-only).
- Archives all GitHub mirrors.
"""
try: try:
gh_conn = rcfiles.github.connect() gh_conn = rcfiles.github.connect()
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
...@@ -219,11 +368,16 @@ def archive_repo(args): ...@@ -219,11 +368,16 @@ def archive_repo(args):
if args.github: if args.github:
repo = gh_conn.repository(owner, name) repo = gh_conn.repository(owner, name)
repo.edit(name, archived=True) repo.edit(name, archived=True)
print(f"Archived repository {repo.html_url}.", file=sys.stderr)
else: else:
project = gl_conn.projects.get(name) project = gl_conn.projects.get(name)
project.archive() project.archive()
print(f"Archived repository {project.web_url}.", file=sys.stderr)
for mirror in github_mirrors(project, gh_conn): for mirror in github_mirrors(project, gh_conn):
mirror.edit(mirror.name, archived=True) mirror.edit(mirror.name, archived=True)
print(
f"Archived GitHub mirror {mirror.html_url}.", file=sys.stderr
)
if __name__ == "__main__": if __name__ == "__main__":
...@@ -236,9 +390,7 @@ if __name__ == "__main__": ...@@ -236,9 +390,7 @@ if __name__ == "__main__":
"new", help="Create a new repository." "new", help="Create a new repository."
) )
parser_create.set_defaults(func=create_repo) parser_create.set_defaults(func=create_repo)
parser_create.add_argument( parser_create.add_argument("name", help="Name of the repository.")
"name", help="Name of the repository.", nargs="?"
)
parser_create.add_argument( parser_create.add_argument(
"-m", "--mirror", help="Setup a mirror in GitHub.", action="store_true" "-m", "--mirror", help="Setup a mirror in GitHub.", action="store_true"
) )
...@@ -247,6 +399,20 @@ if __name__ == "__main__": ...@@ -247,6 +399,20 @@ if __name__ == "__main__":
help="Create the repository in GitHub.", help="Create the repository in GitHub.",
action="store_true", action="store_true",
) )
parser_create.add_argument(
"--internal",
help="Create an internal GitLab repository.",
action="store_true",
)
parser_create.add_argument(
"--private",
help="Create a private GitLab repository.",
action="store_true",
)
parser_create.add_argument(
"--description",
help="Repository description.",
)
parser_mirror = subparsers.add_parser( parser_mirror = subparsers.add_parser(
"mirror", help="Mirror a GitLab repository to GitHub." "mirror", help="Mirror a GitLab repository to GitHub."
......
...@@ -49,18 +49,21 @@ def get_all_remotes(): ...@@ -49,18 +49,21 @@ def get_all_remotes():
def add_remote(repo, name, url): def add_remote(repo, name, url):
"""Add a remote to the Git repository.""" """Add a remote to the Git repository."""
with sh.pushd(repo): with sh.pushd(repo):
try:
git.remote("add", name, url) git.remote("add", name, url)
except sh.ErrorReturnCode_3:
git.remote("set-url", name, url)
def author_name(): def author_name():
"""Get the author name.""" """Get the author name."""
if "GIT_AUTHOR_NAME" in os.environ: if "GIT_AUTHOR_NAME" in os.environ:
return os.environ["GIT_AUTHOR_NAME"] return os.environ["GIT_AUTHOR_NAME"].strip()
return git.config("--get", "user.name") return git.config("--get", "user.name").strip()
def author_email(): def author_email():
"""Get the author email.""" """Get the author email."""
if "GIT_AUTHOR_EMAil" in os.environ: if "GIT_AUTHOR_EMAil" in os.environ:
return os.environ["GIT_AUTHOR_EMAIL"] return os.environ["GIT_AUTHOR_EMAIL"].strip()
return git.config("--get", "user.email") return git.config("--get", "user.email").strip()
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
import os import os
import github3 # pylint: disable=import-error import github3 # pylint: disable=import-error
# from . import git
HTTP_URL = "https://github.com/" HTTP_URL = "https://github.com/"
SSH_URL = "git@github.com:" SSH_URL = "git@github.com:"
...@@ -38,8 +36,3 @@ def connect(): ...@@ -38,8 +36,3 @@ def connect():
def me(conn): def me(conn):
"""Return my GitHub account name.""" """Return my GitHub account name."""
return conn.me().login return conn.me().login
def empty_commit(repository):
"""Commit an empty commit."""
raise NotImplementedError
...@@ -9,7 +9,7 @@ from . import git ...@@ -9,7 +9,7 @@ from . import git
def http_url(conn): def http_url(conn):
"""Return the HTTP url to the GitLab instance.""" """Return the HTTP url to the GitLab instance."""
return conn.get_url() return conn.url
def ssh_url(conn): def ssh_url(conn):
...@@ -21,7 +21,7 @@ def url_to_name(conn, url): ...@@ -21,7 +21,7 @@ def url_to_name(conn, url):
"""Get the full name from the GitLab URL.""" """Get the full name from the GitLab URL."""
return ( return (
url.removeprefix(http_url(conn)) url.removeprefix(http_url(conn))
.removeprefix(ssh_url(conn.git)) .removeprefix(ssh_url(conn))
.removesuffix("/") .removesuffix("/")
.removesuffix(".git") .removesuffix(".git")
) )
...@@ -91,7 +91,7 @@ def me(conn): ...@@ -91,7 +91,7 @@ def me(conn):
def empty_commit(project): def empty_commit(project):
"""Commit an empty commit.""" """Commit an empty commit."""
return project.commit.create( return project.commits.create(
{ {
"id": project.id, "id": project.id,
"branch": project.default_branch, "branch": project.default_branch,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment