From bb9baf658485c10d9d9a3e2693efe1dd236a28a1 Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Wed, 19 Jan 2022 22:08:14 -0500 Subject: [PATCH] gitsrht: Use GraphQL for repository updates/deletion Rewrite the web frontend to use GraphQL mutations where necessary to ensure that GraphQL user webhooks are delivered. Most of the routes defined by scm.sr.ht became part of git.sr.ht. A separate commit will remove those routes from scm.sr.ht to avoid conflicts. As a bonus, a new clone endpoint was added to facilitate easy cloning of third-party repositories. A clone button was added to the user dashboard to make this functionality easily accessible. --- gitsrht/app.py | 5 +- gitsrht/blueprints/api/__init__.py | 50 +++++- gitsrht/blueprints/api/info.py | 158 ++++++++++++++++ gitsrht/blueprints/api/plumbing.py | 2 +- gitsrht/blueprints/api/porcelain.py | 2 +- gitsrht/blueprints/manage.py | 257 +++++++++++++++++++++++---- gitsrht/repos.py | 103 +++++++---- gitsrht/templates/clone.html | 105 +++++++++++ gitsrht/templates/create.html | 97 ++++++++++ gitsrht/templates/dashboard.html | 60 ++++++- gitsrht/templates/settings_info.html | 133 +++++++++++--- gitsrht/templates/summary.html | 5 +- gitsrht/webhooks.py | 1 - 13 files changed, 871 insertions(+), 107 deletions(-) create mode 100644 gitsrht/blueprints/api/info.py create mode 100644 gitsrht/templates/clone.html create mode 100644 gitsrht/templates/create.html diff --git a/gitsrht/app.py b/gitsrht/app.py index e9ccb56..dc0d67e 100644 --- a/gitsrht/app.py +++ b/gitsrht/app.py @@ -23,13 +23,16 @@ class GitApp(ScmSrhtFlask): repository_class=Repository, user_class=User, repo_api=GitRepoApi(), oauth_service=oauth_service) - from gitsrht.blueprints.api import plumbing, porcelain + from gitsrht.blueprints.api import register_api + from gitsrht.blueprints.api.plumbing import plumbing + from gitsrht.blueprints.api.porcelain import porcelain from gitsrht.blueprints.artifacts import artifacts from gitsrht.blueprints.email import mail from gitsrht.blueprints.manage import manage from gitsrht.blueprints.repo import repo from srht.graphql import gql_blueprint + register_api(self) self.register_blueprint(plumbing) self.register_blueprint(porcelain) self.register_blueprint(mail) diff --git a/gitsrht/blueprints/api/__init__.py b/gitsrht/blueprints/api/__init__.py index 9c514de..86c0ee8 100644 --- a/gitsrht/blueprints/api/__init__.py +++ b/gitsrht/blueprints/api/__init__.py @@ -1,2 +1,48 @@ -from gitsrht.blueprints.api.plumbing import plumbing -from gitsrht.blueprints.api.porcelain import porcelain +import pkg_resources +from flask import abort +from scmsrht.access import UserAccess, get_access +from scmsrht.types import Repository, User +from srht.flask import csrf_bypass +from srht.oauth import current_token, oauth + +def get_user(username): + user = None + if username == None: + user = current_token.user + elif username.startswith("~"): + user = User.query.filter(User.username == username[1:]).one_or_none() + if not user: + abort(404) + return user + +def get_repo(owner, reponame, needs=UserAccess.read): + repo = (Repository.query + .filter(Repository.owner_id == owner.id) + .filter(Repository.name == reponame)).one_or_none() + if not repo: + abort(404) + access = get_access(repo, user=current_token.user) + if needs not in access: + abort(403) + return repo + +def register_api(app): + from gitsrht.blueprints.api.info import info + + app.register_blueprint(info) + csrf_bypass(info) + + @app.route("/api/version") + def version(): + try: + dist = pkg_resources.get_distribution("gitsrht") + return { "version": dist.version } + except: + return { "version": "unknown" } + + @app.route("/api/user/") + @app.route("/api/user", defaults={"username": None}) + @oauth(None) + def user_GET(username): + user = get_user(username) + return user.to_dict() diff --git a/gitsrht/blueprints/api/info.py b/gitsrht/blueprints/api/info.py new file mode 100644 index 0000000..c943756 --- /dev/null +++ b/gitsrht/blueprints/api/info.py @@ -0,0 +1,158 @@ +from flask import Blueprint, Response, current_app, request +from scmsrht.access import UserAccess +from scmsrht.repos import RepoVisibility +from scmsrht.types import Access, Repository, User +from gitsrht.blueprints.api import get_user, get_repo +from srht.api import paginated_response +from srht.database import db +from srht.graphql import exec_gql +from srht.oauth import current_token, oauth +from srht.validation import Validation +from sqlalchemy import and_, or_ + +info = Blueprint("api_info", __name__) + +@info.route("/api/repos", defaults={"username": None}) +@info.route("/api//repos") +@oauth("info:read") +def repos_by_user_GET(username): + user = get_user(username) + repos = (Repository.query + .filter(Repository.owner_id == user.id) + .filter(Repository.visibility != RepoVisibility.autocreated)) + if user.id != current_token.user_id: + repos = (repos + .outerjoin(Access._get_current_object(), + Access.repo_id == Repository.id) + .filter(or_( + Access.user_id == current_token.user_id, + and_( + Repository.visibility == RepoVisibility.public, + Access.id.is_(None)) + ))) + return paginated_response(Repository.id, repos) + +@info.route("/api/repos", methods=["POST"]) +@oauth("info:write") +def repos_POST(): + valid = Validation(request) + user = current_token.user + resp = current_app.repo_api.create_repo(valid, user) + if not valid.ok: + return valid.response + return resp, 201 + +@info.route("/api/repos/", defaults={"username": None}) +@info.route("/api//repos/") +@oauth("info:read") +def repos_by_name_GET(username, reponame): + user = get_user(username) + repo = get_repo(user, reponame) + return repo.to_dict() + +@info.route("/api/repos/", methods=["PUT"]) +@oauth("info:write") +def repos_by_name_PUT(reponame): + valid = Validation(request) + user = current_token.user + repo = get_repo(user, reponame, needs=UserAccess.manage) + + rewrite = lambda value: None if value == "" else value + input = { + key: rewrite(valid.source[key]) for key in [ + "name", "description", "visibility", + ] if valid.source.get(key) is not None + } + + # Visibility must be uppercase + if "visibility" in input: + input["visibility"] = input["visibility"].upper() + + resp = exec_gql(current_app.site, """ + mutation UpdateRepository($id: Int!, $input: RepoInput!) { + updateRepository(id: $id, input: $input) { + id + created + updated + name + owner { + canonicalName + ... on User { + name: username + } + } + description + visibility + } + } + """, user=user, valid=valid, id=repo.id, input=input) + + if not valid.ok: + return valid.response + + resp = resp["updateRepository"] + # Convert visibility back to lowercase + resp["visibility"] = resp["visibility"].lower() + return resp + +@info.route("/api/repos/", methods=["DELETE"]) +@oauth("info:write") +def repos_by_name_DELETE(reponame): + user = current_token.user + repo = get_repo(user, reponame, needs=UserAccess.manage) + repo_id = repo.id + current_app.repo_api.delete_repo(repo, user) + return {}, 204 + +@info.route("/api/repos//readme", defaults={"username": None}) +@info.route("/api//repos//readme") +@oauth("info:read") +def repos_by_name_readme_GET(username, reponame): + user = get_user(username) + repo = get_repo(user, reponame) + + if repo.readme is None: + return {}, 404 + else: + return Response(repo.readme, mimetype="text/plain") + +@info.route("/api/repos//readme", methods=["PUT"]) +@oauth("info:write") +def repos_by_name_readme_PUT(reponame): + user = current_token.user + repo = get_repo(user, reponame, needs=UserAccess.manage) + + valid = Validation(request) + if request.content_type != 'text/html': + return valid.error("not text/html", field="content-type") + + readme = None + try: + readme = request.data.decode("utf-8") + except: + return valid.error("README files must be UTF-8 encoded", field="body") + + resp = exec_gql(current_app.site, """ + mutation UpdateRepository($id: Int!, $readme: String!) { + updateRepository(id: $id, input: {readme: $readme}) { id } + } + """, user=user, valid=valid, id=repo.id, readme=readme) + + if not valid.ok: + return valid.response + return {}, 204 + +@info.route("/api/repos//readme", methods=["DELETE"]) +@oauth("info:write") +def repos_by_name_readme_DELETE(reponame): + user = current_token.user + repo = get_repo(user, reponame, needs=UserAccess.manage) + valid = Validation(request) + exec_gql(current_app.site, """ + mutation UpdateRepository($id: Int!) { + updateRepository(id: $id, input: {readme: null}) { id } + } + """, user=user, valid=valid, id=repo.id) + if not valid.ok: + return valid.response + return {}, 204 diff --git a/gitsrht/blueprints/api/plumbing.py b/gitsrht/blueprints/api/plumbing.py index cd250a1..1c57b04 100644 --- a/gitsrht/blueprints/api/plumbing.py +++ b/gitsrht/blueprints/api/plumbing.py @@ -3,7 +3,7 @@ import binascii import pygit2 from flask import Blueprint, Response, abort, request from gitsrht.git import Repository as GitRepository -from scmsrht.blueprints.api import get_user, get_repo +from gitsrht.blueprints.api import get_user, get_repo from srht.oauth import oauth plumbing = Blueprint("api_plumbing", __name__) diff --git a/gitsrht/blueprints/api/porcelain.py b/gitsrht/blueprints/api/porcelain.py index 8da3e8d..600c433 100644 --- a/gitsrht/blueprints/api/porcelain.py +++ b/gitsrht/blueprints/api/porcelain.py @@ -11,7 +11,7 @@ from gitsrht.webhooks import RepoWebhook from io import BytesIO from itertools import groupby from scmsrht.access import UserAccess -from scmsrht.blueprints.api import get_user, get_repo +from gitsrht.blueprints.api import get_user, get_repo from srht.api import paginated_response from srht.database import db from srht.oauth import current_token, oauth diff --git a/gitsrht/blueprints/manage.py b/gitsrht/blueprints/manage.py index f51cc99..d545bc3 100644 --- a/gitsrht/blueprints/manage.py +++ b/gitsrht/blueprints/manage.py @@ -1,47 +1,244 @@ import pygit2 -from flask import Blueprint, request, render_template +from flask import Blueprint, current_app, request, render_template, abort from flask import redirect, url_for from gitsrht.git import Repository as GitRepository +from srht.config import cfg from srht.database import db -from srht.oauth import loginrequired +from srht.flask import session +from srht.graphql import exec_gql, GraphQLError +from srht.oauth import current_user, loginrequired, UserType from srht.validation import Validation from scmsrht.access import check_access, UserAccess +from scmsrht.repos.access import AccessMode from scmsrht.repos.redirect import BaseRedirectMixin from scmsrht.repos.repository import RepoVisibility -from scmsrht.webhooks import UserWebhook +from scmsrht.types import Access, User +import shutil +import os -manage = Blueprint('manage_git', __name__) +manage = Blueprint('manage', __name__) -@manage.route("///settings/info_git", methods=["POST"]) +@manage.route("/create") @loginrequired -def settings_info_git_POST(owner_name, repo_name): +def create_GET(): + another = request.args.get("another") + name = request.args.get("name") + return render_template("create.html", another=another, repo_name=name) + +@manage.route("/create", methods=["POST"]) +@loginrequired +def create_POST(): + if not current_app.repo_api: + abort(501) + valid = Validation(request) + resp = current_app.repo_api.create_repo(valid) + if not valid.ok: + return render_template("create.html", **valid.kwargs) + + another = valid.optional("another") + if another == "on": + return redirect("/create?another") + else: + return redirect(url_for("repo.summary", + owner=current_user.canonical_name, repo=resp["name"])) + +@manage.route("/clone") +@loginrequired +def clone(): + another = request.args.get("another") + return render_template("clone.html", another=another, visibility="UNLISTED") + +@manage.route("/clone", methods=["POST"]) +@loginrequired +def clone_POST(): + if not current_app.repo_api: + abort(501) + valid = Validation(request) + resp = current_app.repo_api.clone_repo(valid) + if not valid.ok: + return render_template("clone.html", **valid.kwargs) + return redirect(url_for("repo.summary", + owner=current_user.canonical_name, repo=resp["name"])) + +@manage.route("///settings/info") +@loginrequired +def settings_info(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + return redirect(url_for(".settings_info", + owner_name=owner_name, repo_name=repo.new_repo.name)) + return render_template("settings_info.html", owner=owner, repo=repo) + +@manage.route("///settings/info", methods=["POST"]) +@loginrequired +def settings_info_POST(owner_name, repo_name): owner, repo = check_access(owner_name, repo_name, UserAccess.manage) if isinstance(repo, BaseRedirectMixin): repo = repo.new_repo + + # TODO: GraphQL mutation to set default branch name valid = Validation(request) - desc = valid.optional("description", default=repo.description) - visibility = valid.optional("visibility", - cls=RepoVisibility, - default=repo.visibility) branch = valid.optional("default_branch_name") - with GitRepository(repo.path) as git_repo: - new_default_branch = None - if branch: - try: - new_default_branch = git_repo.branches.get(branch) - except pygit2.InvalidSpecError: + if branch: + with GitRepository(repo.path) as git_repo: + new_default_branch = git_repo.branches.get(branch) + if new_default_branch: + head_ref = git_repo.lookup_reference("HEAD") + head_ref.set_target(new_default_branch.name) + else: valid.error(f"Branch {branch} not found", field="default_branch_name") - if not valid.ok: - return render_template("settings_info.html", - owner=owner, repo=repo, **valid.kwargs) - if new_default_branch: - head_ref = git_repo.lookup_reference("HEAD") - head_ref.set_target(new_default_branch.name) - - repo.visibility = visibility - repo.description = desc - UserWebhook.deliver(UserWebhook.Events.repo_update, - repo.to_dict(), UserWebhook.Subscription.user_id == repo.owner_id) - db.session.commit() - return redirect(url_for("manage.settings_info", - owner_name=owner_name, repo_name=repo_name)) + return render_template("settings_info.html", + owner=owner, repo=repo, **valid.kwargs) + + rewrite = lambda value: None if value == "" else value + input = { + key: rewrite(valid.source[key]) for key in [ + "description", "visibility", + ] if valid.source.get(key) is not None + } + + resp = exec_gql("git.sr.ht", """ + mutation UpdateRepository($id: Int!, $input: RepoInput!) { + updateRepository(id: $id, input: $input) { id } + } + """, valid=valid, id=repo.id, input=input) + if not valid.ok: + return render_template("settings_info.html", + owner=owner, repo=repo, **valid.kwargs) + + return redirect(url_for("manage.settings_info", + owner_name=owner_name, repo_name=repo_name)) + +@manage.route("///settings/rename") +@loginrequired +def settings_rename(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + return redirect(url_for(".settings_rename", + owner_name=owner_name, repo_name=repo.new_repo.name)) + return render_template("settings_rename.html", owner=owner, repo=repo) + +@manage.route("///settings/rename", methods=["POST"]) +@loginrequired +def settings_rename_POST(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + repo = repo.new_repo + + valid = Validation(request) + name = valid.require("name") + if not valid.ok: + return render_template("settings_rename.html", owner=owner, repo=repo, + **valid.kwargs) + + resp = None + try: + resp = exec_gql("git.sr.ht", """ + mutation RenameRepository($id: Int!, $name: String!) { + updateRepository(id: $id, input: {name: $name}) { + name + } + } + """, id=repo.id, name=name) + except GraphQLError as e: + for err in e.errors: + valid.error(err["message"], field="name") + + if not valid.ok: + return render_template("settings_rename.html", owner=owner, repo=repo, + **valid.kwargs) + resp = resp["updateRepository"] + return redirect(url_for("repo.summary", + owner=owner_name, repo=resp["name"])) + +@manage.route("///settings/access") +@loginrequired +def settings_access(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + return redirect(url_for(".settings_manage", + owner_name=owner_name, repo_name=repo.new_repo.name)) + return render_template("settings_access.html", owner=owner, repo=repo) + +@manage.route("///settings/access", methods=["POST"]) +@loginrequired +def settings_access_POST(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + repo = repo.new_repo + valid = Validation(request) + username = valid.require("user", friendly_name="User") + mode = valid.optional("access", cls=AccessMode, default=AccessMode.ro) + if not valid.ok: + return render_template("settings_access.html", + owner=owner, repo=repo, **valid.kwargs) + # TODO: Group access + if username[0] == "~": + username = username[1:] + try: + user = current_app.oauth_service.lookup_user(username) + except: + user = None + valid.expect(user, "User not found.", field="user") + valid.expect(not user or user.id != current_user.id, + "You can't adjust your own access controls. You always have full read/write access.", + field="user") + valid.expect(not user or user.user_type != UserType.unconfirmed, + "This account has not been confirmed yet.", field="user") + valid.expect(not user or user.user_type != UserType.suspended, + "This account has been suspended.", field="user") + if not valid.ok: + return render_template("settings_access.html", + owner=owner, repo=repo, **valid.kwargs) + grant = (Access.query + .filter(Access.repo_id == repo.id, Access.user_id == user.id) + ).first() + if not grant: + grant = Access() + grant.repo_id = repo.id + grant.user_id = user.id + db.session.add(grant) + grant.mode = mode + db.session.commit() + return redirect(url_for("manage.settings_access", + owner_name=owner.canonical_name, repo_name=repo.name)) + +@manage.route("///settings/access/revoke/", methods=["POST"]) +@loginrequired +def settings_access_revoke_POST(owner_name, repo_name, grant_id): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + repo = repo.new_repo + grant = (Access.query + .filter(Access.repo_id == repo.id, Access.id == grant_id) + ).first() + if not grant: + abort(404) + db.session.delete(grant) + db.session.commit() + return redirect("/{}/{}/settings/access".format( + owner.canonical_name, repo.name)) + +@manage.route("///settings/delete") +@loginrequired +def settings_delete(owner_name, repo_name): + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + return redirect(url_for(".settings_delete", + owner_name=owner_name, repo_name=repo.new_repo.name)) + return render_template("settings_delete.html", owner=owner, repo=repo) + +@manage.route("///settings/delete", methods=["POST"]) +@loginrequired +def settings_delete_POST(owner_name, repo_name): + if not current_app.repo_api: + abort(501) + owner, repo = check_access(owner_name, repo_name, UserAccess.manage) + if isinstance(repo, BaseRedirectMixin): + # Normally we'd redirect but we don't want to fuck up some other repo + abort(404) + repo_id = repo.id + current_app.repo_api.delete_repo(repo) + session["notice"] = "{}/{} was deleted.".format( + owner.canonical_name, repo.name) + return redirect("/" + owner.canonical_name) diff --git a/gitsrht/repos.py b/gitsrht/repos.py index 74d1e1d..aff1799 100644 --- a/gitsrht/repos.py +++ b/gitsrht/repos.py @@ -1,12 +1,15 @@ import hashlib import os.path import pygit2 +import re +import shutil import subprocess from gitsrht.types import Artifact, Repository, Redirect from minio import Minio -from scmsrht.repos import SimpleRepoApi +from scmsrht.repos.repository import RepoVisibility from srht.config import cfg from srht.database import db +from srht.graphql import exec_gql, GraphQLError from werkzeug.utils import secure_filename repos_path = cfg("git.sr.ht", "repos") @@ -75,44 +78,68 @@ def upload_artifact(valid, repo, commit, f, filename): db.session.add(artifact) return artifact -class GitRepoApi(SimpleRepoApi): - def __init__(self): - super().__init__(repos_path, - redirect_class=Redirect, - repository_class=Repository) +# TODO: Remove repo API wrapper class - def do_init_repo(self, owner, repo): - # Note: update gitsrht-shell when changing this, - # do_clone_repo(), or _repo_config_init() - git_repo = pygit2.init_repository(repo.path, bare=True, - flags=pygit2.GIT_REPOSITORY_INIT_BARE | - pygit2.GIT_REPOSITORY_INIT_MKPATH) - self._repo_config_init(repo, git_repo) +class GitRepoApi(): + def get_repo_path(self, owner, repo_name): + return os.path.join(repos_path, "~" + owner.username, repo_name) - def _repo_config_init(self, repo, git_repo): - git_repo.config["srht.repo-id"] = repo.id - # We handle this ourselves in the post-update hook, and git's - # default behaviour is to print a large notice and reject the push entirely - git_repo.config["receive.denyDeleteCurrent"] = "ignore" - git_repo.config["receive.advertisePushOptions"] = True - os.unlink(os.path.join(repo.path, "info", "exclude")) - os.unlink(os.path.join(repo.path, "hooks", "README.sample")) - os.unlink(os.path.join(repo.path, "description")) - os.symlink(post_update, os.path.join(repo.path, "hooks", "pre-receive")) - os.symlink(post_update, os.path.join(repo.path, "hooks", "update")) - os.symlink(post_update, os.path.join(repo.path, "hooks", "post-update")) + def create_repo(self, valid, user=None): + repo_name = valid.require("name", friendly_name="Name") + description = valid.optional("description") + visibility = valid.optional("visibility") + if not valid.ok: + return None - def do_delete_repo(self, repo): - from gitsrht.webhooks import RepoWebhook - RepoWebhook.Subscription.query.filter( - RepoWebhook.Subscription.repo_id == repo.id).delete() - # TODO: Should we delete these asynchronously? - for artifact in (Artifact.query - .filter(Artifact.user_id == repo.owner_id) - .filter(Artifact.repo_id == repo.id)): - delete_artifact(artifact) - super().do_delete_repo(repo) + resp = exec_gql("git.sr.ht", """ + mutation CreateRepository($name: String!, $visibility: Visibility = PUBLIC, $description: String) { + createRepository(name: $name, visibility: $visibility, description: $description) { + id + created + updated + name + owner { + canonicalName + ... on User { + name: username + } + } + description + visibility + } + } + """, valid=valid, user=user, name=repo_name, description=description, visibility=visibility) - def do_clone_repo(self, source, repo): - git_repo = pygit2.clone_repository(source, repo.path, bare=True) - self._repo_config_init(repo, git_repo) + print(valid) + + if not valid.ok: + return None + return resp["createRepository"] + + def clone_repo(self, valid): + cloneUrl = valid.require("cloneUrl", friendly_name="Clone URL") + name = valid.require("name", friendly_name="Name") + description = valid.optional("description") + visibility = valid.optional("visibility") + if not valid.ok: + return None + + resp = exec_gql("git.sr.ht", """ + mutation CreateRepository($name: String!, $visibility: Visibility = UNLISTED, $description: String, $cloneUrl: String) { + createRepository(name: $name, visibility: $visibility, description: $description, cloneUrl: $cloneUrl) { + name + } + } + """, valid=valid, name=name, visibility=visibility, + description=description, cloneUrl=cloneUrl) + + if not valid.ok: + return None + return resp["createRepository"] + + def delete_repo(self, repo, user=None): + exec_gql("git.sr.ht", """ + mutation DeleteRepository($id: Int!) { + deleteRepository(id: $id) { id } + } + """, user=user, id=repo.id) diff --git a/gitsrht/templates/clone.html b/gitsrht/templates/clone.html new file mode 100644 index 0000000..5629e6c --- /dev/null +++ b/gitsrht/templates/clone.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

Clone existing repository

+
+ {{csrf_token()}} +
+ + + {{valid.summary("cloneUrl")}} +
+
+ + + {{valid.summary("name")}} +
+
+ + + {{valid.summary("description")}} +
+
+ Visibility +
+ +
+
+ +
+
+ +
+
+ + +
+
+
+
+{% endblock %} diff --git a/gitsrht/templates/create.html b/gitsrht/templates/create.html new file mode 100644 index 0000000..5fdf1a5 --- /dev/null +++ b/gitsrht/templates/create.html @@ -0,0 +1,97 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

Create new repository

+
+ {{csrf_token()}} +
+ + + {{valid.summary("name")}} +
+
+ + + {{valid.summary("description")}} +
+
+ Visibility +
+ +
+
+ +
+
+ +
+
+ + +
+
+
+
+ Did you know? If you remember how our clone URLs are + written, you can push to repositories that don't exist — they'll + be automatically created for you. +
+
+
+
+{% endblock %} diff --git a/gitsrht/templates/dashboard.html b/gitsrht/templates/dashboard.html index d618ef6..f25b119 100644 --- a/gitsrht/templates/dashboard.html +++ b/gitsrht/templates/dashboard.html @@ -1,6 +1,56 @@ -{% extends "bases/scmdashboard.html" %} -{% block welcome_user %} - Welcome back, {{ current_user.username }}! This is your git hosting service. - Documentation for its use is - available here. +{% extends "base.html" %} + +{% block content %} +
+
+
+

+ Welcome back, {{ current_user.username }}! This is your git hosting service. + Documentation for its use is + available here. +

+ Create new repository {{icon("caret-right")}} + Clone existing repository {{icon("caret-right")}} +
+
+
+ {% if repos and len(repos) %} +
+ {% for repo in repos %} +
+

+ ~{{current_user.username}}/{{repo.name}} + {% if repo.visibility.value != 'public' %} + + {{ repo.visibility.value }} + + {% endif %} +

+ {% if repo.description %} +

{{ repo.description }}

+ {% endif %} +
+ {% endfor %} +
+ More on your profile {{icon("caret-right")}} + {% else %} +

You don't have any repositories.

+ {% endif %} +
+
+
{% endblock %} diff --git a/gitsrht/templates/settings_info.html b/gitsrht/templates/settings_info.html index 22ae154..72434c3 100644 --- a/gitsrht/templates/settings_info.html +++ b/gitsrht/templates/settings_info.html @@ -1,30 +1,109 @@ -{% set info_action=url_for("manage_git.settings_info_git_POST", - owner_name=owner.canonical_name, repo_name=repo.name) %} -{% extends "bases/scmsettings_info.html" %} +{% extends "settings.html" %} -{% block extrafields %} -
- - - {{valid.summary('default_branch_name')}} +{% block content %} +
+
+
+ {{csrf_token()}} +
+ + +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + {{valid.summary('default_branch_name')}} +
+ +
+
{% endblock %} diff --git a/gitsrht/templates/summary.html b/gitsrht/templates/summary.html index 42bef88..c980974 100644 --- a/gitsrht/templates/summary.html +++ b/gitsrht/templates/summary.html @@ -89,7 +89,10 @@ {% elif current_user != repo.owner %}
{{csrf_token()}} - + + + + diff --git a/gitsrht/webhooks.py b/gitsrht/webhooks.py index 8a203fe..c857698 100644 --- a/gitsrht/webhooks.py +++ b/gitsrht/webhooks.py @@ -8,7 +8,6 @@ if not hasattr(db, "session"): from srht.webhook import Event from srht.webhook.celery import CeleryWebhook, make_worker from srht.metrics import RedisQueueCollector -from scmsrht.webhooks import UserWebhook import sqlalchemy as sa webhook_broker = cfg("git.sr.ht", "webhooks") -- 2.38.4