app.py
Python script, Unicode text, UTF-8 text executable
1__version__ = "0.2.0" 2 3import os 4import shutil 5import random 6import subprocess 7import platform 8 9import PIL 10import git 11import mimetypes 12import magic 13import flask 14import cairosvg 15import celery 16import shlex 17from functools import wraps 18from datetime import datetime 19from enum import Enum 20from cairosvg import svg2png 21from flask_sqlalchemy import SQLAlchemy 22from flask_bcrypt import Bcrypt 23from markupsafe import escape, Markup 24from flask_migrate import Migrate 25from PIL import Image 26from flask_httpauth import HTTPBasicAuth 27import config 28from flask_babel import Babel, gettext, ngettext, force_locale 29 30_ = gettext 31n_ = gettext 32 33app = flask.Flask(__name__) 34app.config.from_mapping( 35CELERY=dict( 36broker_url=config.REDIS_URI, 37result_backend=config.REDIS_URI, 38task_ignore_result=True, 39), 40) 41 42auth = HTTPBasicAuth() 43 44app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 45app.config["SECRET_KEY"] = config.DB_PASSWORD 46app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 47app.config["BABEL_TRANSLATION_DIRECTORIES"] = "i18n" 48app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE 49 50db = SQLAlchemy(app) 51bcrypt = Bcrypt(app) 52migrate = Migrate(app, db) 53 54from misc_utils import * 55 56import git_http 57import jinja_utils 58import celery_tasks 59from celery import Celery, Task 60import celery_integration 61import pathlib 62 63from models import * 64 65babel = Babel(app) 66 67 68def get_locale(): 69if flask.request.cookies.get("language"): 70return flask.request.cookies.get("language") 71return flask.request.accept_languages.best_match(config.available_locales) 72 73 74babel.init_app(app, locale_selector=get_locale) 75 76with app.app_context(): 77locale_names = {} 78for language in config.available_locales: 79with force_locale(language): 80# NOTE: Translate this to the language's name in that language, for example in French you would use français 81locale_names[language] = gettext("English") 82 83worker = celery_integration.init_celery_app(app) 84 85repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 86 87app.jinja_env.add_extension("jinja2.ext.do") 88app.jinja_env.add_extension("jinja2.ext.loopcontrols") 89app.jinja_env.add_extension("jinja2.ext.debug") 90 91 92@app.context_processor 93def default(): 94username = flask.session.get("username") 95 96user_object = User.query.filter_by(username=username).first() 97 98return { 99"logged_in_user": username, 100"user_object": user_object, 101"Notification": Notification, 102"unread": UserNotification.query.filter_by(user_username=username).filter( 103UserNotification.attention_level > 0).count(), 104"config": config, 105"Markup": Markup, 106"locale_names": locale_names, 107} 108 109 110@app.route("/") 111def main(): 112if flask.session.get("username"): 113return flask.render_template("home.html") 114else: 115return flask.render_template("no-home.html") 116 117 118@app.route("/userstyle") 119def userstyle(): 120if flask.session.get("username") and os.path.exists( 121os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config", 122"theme.css")): 123return flask.send_from_directory( 124os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config"), 125"theme.css") 126else: 127return flask.Response("", mimetype="text/css") 128 129 130@app.route("/about/") 131def about(): 132return flask.render_template("about.html", platform=platform, version=__version__) 133 134 135@app.route("/search") 136def search(): 137query = flask.request.args.get("q") 138if not query: 139query = "" 140 141results = Repo.query.filter(Repo.name.ilike(f"%{query}%")).filter_by(visibility=2).all() 142 143return flask.render_template("search.html", results=results, query=query) 144 145 146@app.route("/language", methods=["POST"]) 147def set_locale(): 148response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 149code=303) 150if not flask.request.form.get("language"): 151response.delete_cookie("language") 152else: 153response.set_cookie("language", flask.request.form.get("language")) 154 155return response 156 157 158@app.route("/cookie-dismiss") 159def dismiss_banner(): 160response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 161code=303) 162response.set_cookie("cookie-banner", "1") 163return response 164 165 166@app.route("/help/") 167def help_redirect(): 168return flask.redirect(config.help_url, code=302) 169 170 171@app.route("/settings/") 172def settings(): 173if not flask.session.get("username"): 174flask.abort(401) 175user = User.query.filter_by(username=flask.session.get("username")).first() 176 177return flask.render_template("user-settings.html", user=user) 178 179 180@app.route("/settings/profile", methods=["POST"]) 181def settings_profile(): 182user = User.query.filter_by(username=flask.session.get("username")).first() 183 184user.display_name = flask.request.form["displayname"] 185user.URL = flask.request.form["url"] 186user.company = flask.request.form["company"] 187user.company_URL = flask.request.form["companyurl"] 188user.email = flask.request.form.get("email") if flask.request.form.get( 189"email") else None 190user.location = flask.request.form["location"] 191user.show_mail = True if flask.request.form.get("showmail") else False 192user.bio = flask.request.form.get("bio") 193 194db.session.commit() 195 196flask.flash( 197Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), 198category="success") 199return flask.redirect(f"/{flask.session.get('username')}", code=303) 200 201 202@app.route("/settings/preferences", methods=["POST"]) 203def settings_prefs(): 204user = User.query.filter_by(username=flask.session.get("username")).first() 205 206user.default_page_length = int(flask.request.form["page_length"]) 207 208db.session.commit() 209 210flask.flash( 211Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), 212category="success") 213return flask.redirect(f"/{flask.session.get('username')}", code=303) 214 215 216@app.route("/favourites/", methods=["GET", "POST"]) 217def favourites(): 218if not flask.session.get("username"): 219flask.abort(401) 220if flask.request.method == "GET": 221relationships = RepoFavourite.query.filter_by( 222user_username=flask.session.get("username")) 223 224return flask.render_template("favourites.html", favourites=relationships) 225 226 227@app.route("/favourites/<int:id>", methods=["POST"]) 228def favourite_edit(id): 229if not flask.session.get("username"): 230flask.abort(401) 231favourite = db.session.get(RepoFavourite, id) 232if favourite.user_username != flask.session.get("username"): 233flask.abort(403) 234data = flask.request.form 235print(data) 236favourite.notify_commit = js_to_bool(data.get("commit")) 237favourite.notify_forum = js_to_bool(data.get("forum")) 238favourite.notify_pr = js_to_bool(data.get("pull_request")) 239favourite.notify_admin = js_to_bool(data.get("administrative")) 240print(favourite.notify_commit, favourite.notify_forum, favourite.notify_pr, 241favourite.notify_admin) 242db.session.commit() 243return flask.render_template_string( 244""" 245<tr hx-post="/favourites/{{ favourite.id }}" hx-trigger="change" hx-include="#commit-{{ favourite.id }}, #forum-{{ favourite.id }}, #pull_request-{{ favourite.id }}, #administrative-{{ favourite.id }}" hx-headers='{"Content-Type": "application/json"}' hx-swap="outerHTML"> 246<td><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></td> 247<td style="text-align: center;"><input type="checkbox" name="commit" id="commit-{{ favourite.id }}" value="true" {% if favourite.notify_commit %}checked{% endif %}></td> 248<td style="text-align: center;"><input type="checkbox" name="forum" id="forum-{{ favourite.id }}" value="true" {% if favourite.notify_forum %}checked{% endif %}></td> 249<td style="text-align: center;"><input type="checkbox" name="pull_request" id="pull_request-{{ favourite.id }}" value="true" {% if favourite.notify_pr %}checked{% endif %}></td> 250<td style="text-align: center;"><input type="checkbox" name="administrative" id="administrative-{{ favourite.id }}" value="true" {% if favourite.notify_admin %}checked{% endif %}></td> 251</tr> 252""", 253favourite=favourite 254) 255 256 257@app.route("/notifications/", methods=["GET", "POST"]) 258def notifications(): 259if not flask.session.get("username"): 260flask.abort(401) 261if flask.request.method == "GET": 262return flask.render_template("notifications.html", 263notifications=UserNotification.query.filter_by( 264user_username=flask.session.get("username") 265).order_by(UserNotification.id.desc()), 266db=db, Commit=Commit 267) 268 269 270@app.route("/notifications/<int:notification_id>/read", methods=["POST"]) 271def mark_read(notification_id): 272if not flask.session.get("username"): 273flask.abort(401) 274notification = UserNotification.query.filter_by(id=notification_id).first() 275if notification.user_username != flask.session.get("username"): 276flask.abort(403) 277notification.mark_read() 278db.session.commit() 279return flask.render_template_string( 280"<button hx-post='/notifications/{{ notification.id }}/unread' hx-swap='outerHTML'>Mark as unread</button>", 281notification=notification), 200 282 283 284@app.route("/notifications/<int:notification_id>/unread", methods=["POST"]) 285def mark_unread(notification_id): 286if not flask.session.get("username"): 287flask.abort(401) 288notification = UserNotification.query.filter_by(id=notification_id).first() 289if notification.user_username != flask.session.get("username"): 290flask.abort(403) 291notification.mark_unread() 292db.session.commit() 293return flask.render_template_string( 294"<button hx-post='/notifications/{{ notification.id }}/read' hx-swap='outerHTML'>Mark as read</button>", 295notification=notification), 200 296 297 298@app.route("/notifications/mark-all-read", methods=["POST"]) 299def mark_all_read(): 300if not flask.session.get("username"): 301flask.abort(401) 302 303notifications = UserNotification.query.filter_by( 304user_username=flask.session.get("username")) 305for notification in notifications: 306notification.mark_read() 307db.session.commit() 308return flask.redirect("/notifications/", code=303) 309 310 311@app.route("/accounts/", methods=["GET", "POST"]) 312def login(): 313if flask.request.method == "GET": 314return flask.render_template("login.html") 315else: 316if "login" in flask.request.form: 317username = flask.request.form["username"] 318password = flask.request.form["password"] 319 320user = User.query.filter_by(username=username).first() 321 322if user and bcrypt.check_password_hash(user.password_hashed, password): 323flask.session["username"] = user.username 324flask.flash( 325Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _( 326"Successfully logged in as {username}").format( 327username=username)), 328category="success") 329return flask.redirect("/", code=303) 330elif not user: 331flask.flash(Markup( 332"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 333"User not found")), 334category="alert") 335return flask.render_template("login.html") 336else: 337flask.flash(Markup( 338"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 339"Invalid password")), 340category="error") 341return flask.render_template("login.html") 342if "signup" in flask.request.form: 343username = flask.request.form["username"] 344password = flask.request.form["password"] 345password2 = flask.request.form["password2"] 346email = flask.request.form.get("email") 347email2 = flask.request.form.get("email2") # repeat email is a honeypot 348name = flask.request.form.get("name") 349 350if not only_chars(username, 351"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 352flask.flash(Markup( 353_("Usernames may only contain Latin alphabet, numbers, '-' and '_'")), 354category="error") 355return flask.render_template("login.html") 356 357if username in config.RESERVED_NAMES: 358flask.flash( 359Markup( 360"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _( 361"Sorry, {username} is a system path").format( 362username=username)), 363category="error") 364return flask.render_template("login.html") 365 366user_check = User.query.filter_by(username=username).first() 367if user_check or email2: # make the honeypot look like a normal error 368flask.flash( 369Markup( 370"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _( 371"The username {username} is taken").format( 372username=username)), 373category="error") 374return flask.render_template("login.html") 375 376if password2 != password: 377flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>" + _( 378"Make sure the passwords match")), 379category="error") 380return flask.render_template("login.html") 381 382user = User(username, password, email, name) 383db.session.add(user) 384db.session.commit() 385flask.session["username"] = user.username 386flask.flash(Markup( 387"<iconify-icon icon='mdi:account'></iconify-icon>" + _( 388"Successfully created and logged in as {username}").format( 389username=username)), 390category="success") 391 392notification = Notification({"type": "welcome"}) 393db.session.add(notification) 394db.session.commit() 395 396return flask.redirect("/", code=303) 397 398 399@app.route("/newrepo/", methods=["GET", "POST"]) 400def new_repo(): 401if not flask.session.get("username"): 402flask.abort(401) 403if flask.request.method == "GET": 404return flask.render_template("new-repo.html") 405else: 406name = flask.request.form["name"] 407visibility = int(flask.request.form["visibility"]) 408 409if not only_chars(name, 410"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 411flask.flash(Markup( 412"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 413"Repository names may only contain Latin alphabet, numbers, '-' and '_'")), 414category="error") 415return flask.render_template("new-repo.html") 416 417user = User.query.filter_by(username=flask.session.get("username")).first() 418 419repo = Repo(user, name, visibility) 420db.session.add(repo) 421db.session.commit() 422 423flask.flash(Markup(_("Successfully created repository {name}").format(name=name)), 424category="success") 425return flask.redirect(repo.route, code=303) 426 427 428@app.route("/logout") 429def logout(): 430flask.session.clear() 431flask.flash(Markup( 432"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")), 433category="info") 434return flask.redirect("/", code=303) 435 436 437@app.route("/<username>/", methods=["GET", "POST"]) 438def user_profile(username): 439old_relationship = UserFollow.query.filter_by( 440follower_username=flask.session.get("username"), 441followed_username=username).first() 442if flask.request.method == "GET": 443user = User.query.filter_by(username=username).first() 444match flask.request.args.get("action"): 445case "repositories": 446repos = Repo.query.filter_by(owner_name=username, visibility=2) 447return flask.render_template("user-profile-repositories.html", user=user, 448repos=repos, 449relationship=old_relationship) 450case "followers": 451return flask.render_template("user-profile-followers.html", user=user, 452relationship=old_relationship) 453case "follows": 454return flask.render_template("user-profile-follows.html", user=user, 455relationship=old_relationship) 456case _: 457return flask.render_template("user-profile-overview.html", user=user, 458relationship=old_relationship) 459 460elif flask.request.method == "POST": 461match flask.request.args.get("action"): 462case "follow": 463if username == flask.session.get("username"): 464flask.abort(403) 465if old_relationship: 466db.session.delete(old_relationship) 467else: 468relationship = UserFollow( 469flask.session.get("username"), 470username 471) 472db.session.add(relationship) 473db.session.commit() 474 475user = db.session.get(User, username) 476author = db.session.get(User, flask.session.get("username")) 477notification = Notification({"type": "update", "version": "0.0.0"}) 478db.session.add(notification) 479db.session.commit() 480 481db.session.commit() 482return flask.redirect("?", code=303) 483 484 485@app.route("/<username>/<repository>/") 486def repository_index(username, repository): 487return flask.redirect("./tree", code=302) 488 489 490@app.route("/info/<username>/avatar") 491def user_avatar(username): 492server_userdata_location = os.path.join(config.USERDATA_PATH, username) 493if not os.path.exists(server_userdata_location): 494return flask.render_template("not-found.html"), 404 495 496return flask.send_from_directory(server_userdata_location, "avatar.png") 497 498 499@app.route("/info/<username>/avatar", methods=["POST"]) 500def user_avatar_upload(username): 501server_userdata_location = os.path.join(config.USERDATA_PATH, username) 502 503if not os.path.exists(server_userdata_location): 504flask.abort(404) 505if not flask.session.get("username") == username: 506flask.abort(403) 507 508# Convert image to PNG 509try: 510image = Image.open(flask.request.files["avatar"]) 511except PIL.UnidentifiedImageError: 512flask.abort(400) 513image.save(os.path.join(server_userdata_location, "avatar.png")) 514 515return flask.redirect(f"/{username}", code=303) 516 517 518@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 519def repository_raw(username, repository, branch, subpath): 520server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 521if not os.path.exists(server_repo_location): 522app.logger.error(f"Cannot load {server_repo_location}") 523flask.abort(404) 524if not (get_visibility(username, repository) or get_permission_level( 525flask.session.get("username"), username, 526repository) is not None): 527flask.abort(403) 528 529app.logger.info(f"Loading {server_repo_location}") 530 531if not os.path.exists(server_repo_location): 532app.logger.error(f"Cannot load {server_repo_location}") 533return flask.render_template("not-found.html"), 404 534 535repo = git.Repo(server_repo_location) 536repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 537if not repo_data.default_branch: 538if repo.heads: 539repo_data.default_branch = repo.heads[0].name 540else: 541return flask.render_template("empty.html", 542remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 543if not branch: 544branch = repo_data.default_branch 545return flask.redirect(f"./{branch}", code=302) 546 547if branch.startswith("tag:"): 548ref = f"tags/{branch[4:]}" 549elif branch.startswith("~"): 550ref = branch[1:] 551else: 552ref = f"heads/{branch}" 553 554ref = ref.replace("~", "/") # encode slashes for URL support 555 556try: 557repo.git.checkout("-f", ref) 558except git.exc.GitCommandError: 559return flask.render_template("not-found.html"), 404 560 561return flask.send_from_directory(config.REPOS_PATH, 562os.path.join(username, repository, subpath)) 563 564 565@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 566@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 567@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 568def repository_tree(username, repository, branch, subpath): 569server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 570if not os.path.exists(server_repo_location): 571app.logger.error(f"Cannot load {server_repo_location}") 572flask.abort(404) 573if not (get_visibility(username, repository) or get_permission_level( 574flask.session.get("username"), username, 575repository) is not None): 576flask.abort(403) 577 578app.logger.info(f"Loading {server_repo_location}") 579 580repo = git.Repo(server_repo_location) 581repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 582if not repo_data.default_branch: 583if repo.heads: 584repo_data.default_branch = repo.heads[0].name 585else: 586return flask.render_template("empty.html", 587remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 588if not branch: 589branch = repo_data.default_branch 590return flask.redirect(f"./{branch}", code=302) 591 592if branch.startswith("tag:"): 593ref = f"tags/{branch[4:]}" 594elif branch.startswith("~"): 595ref = branch[1:] 596else: 597ref = f"heads/{branch}" 598 599ref = ref.replace("~", "/") # encode slashes for URL support 600 601try: 602repo.git.checkout("-f", ref) 603except git.exc.GitCommandError: 604return flask.render_template("not-found.html"), 404 605 606branches = repo.heads 607 608all_refs = [] 609for ref in repo.heads: 610all_refs.append((ref, "head")) 611for ref in repo.tags: 612all_refs.append((ref, "tag")) 613 614if os.path.isdir(os.path.join(server_repo_location, subpath)): 615files = [] 616blobs = [] 617 618for entry in os.listdir(os.path.join(server_repo_location, subpath)): 619if not os.path.basename(entry) == ".git": 620files.append(os.path.join(subpath, entry)) 621 622infos = [] 623 624for file in files: 625path = os.path.join(server_repo_location, file) 626mimetype = guess_mime(path) 627 628text = git_command(server_repo_location, None, "log", "--format='%H\n'", 629shlex.quote(file)).decode() 630 631sha = text.split("\n")[0] 632identifier = f"/{username}/{repository}/{sha}" 633 634last_commit = db.session.get(Commit, identifier) 635 636info = { 637"name": os.path.basename(file), 638"serverPath": path, 639"relativePath": file, 640"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 641"size": human_size(os.path.getsize(path)), 642"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 643"commit": last_commit, 644"shaSize": 7, 645} 646 647special_icon = config.match_icon(os.path.basename(file)) 648if special_icon: 649info["icon"] = special_icon 650elif os.path.isdir(path): 651info["icon"] = config.folder_icon 652elif mimetypes.guess_type(path)[0] in config.file_icons: 653info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]] 654else: 655info["icon"] = config.unknown_icon 656 657if os.path.isdir(path): 658infos.insert(0, info) 659else: 660infos.append(info) 661 662return flask.render_template( 663"repo-tree.html", 664username=username, 665repository=repository, 666files=infos, 667subpath=os.path.join("/", subpath), 668branches=all_refs, 669current=branch, 670remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 671is_favourite=get_favourite(flask.session.get("username"), username, repository) 672) 673else: 674path = os.path.join(server_repo_location, subpath) 675 676if not os.path.exists(path): 677return flask.render_template("not-found.html"), 404 678 679mimetype = guess_mime(path) 680mode = mimetype.split("/", 1)[0] 681size = human_size(os.path.getsize(path)) 682 683special_icon = config.match_icon(os.path.basename(path)) 684if special_icon: 685icon = special_icon 686elif os.path.isdir(path): 687icon = config.folder_icon 688elif mimetypes.guess_type(path)[0] in config.file_icons: 689icon = config.file_icons[mimetypes.guess_type(path)[0]] 690else: 691icon = config.unknown_icon 692 693contents = None 694if mode == "text": 695contents = convert_to_html(path) 696 697return flask.render_template( 698"repo-file.html", 699username=username, 700repository=repository, 701file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 702branches=all_refs, 703current=branch, 704mode=mode, 705mimetype=mimetype, 706detailedtype=magic.from_file(path), 707size=size, 708icon=icon, 709subpath=os.path.join("/", subpath), 710extension=pathlib.Path(path).suffix, 711basename=os.path.basename(path), 712contents=contents, 713remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 714is_favourite=get_favourite(flask.session.get("username"), username, repository) 715) 716 717 718@repositories.route("/<username>/<repository>/commit/<sha>") 719def repository_commit(username, repository, sha): 720server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 721if not os.path.exists(server_repo_location): 722app.logger.error(f"Cannot load {server_repo_location}") 723flask.abort(404) 724if not (get_visibility(username, repository) or get_permission_level( 725flask.session.get("username"), username, 726repository) is not None): 727flask.abort(403) 728 729app.logger.info(f"Loading {server_repo_location}") 730 731if not os.path.exists(server_repo_location): 732app.logger.error(f"Cannot load {server_repo_location}") 733return flask.render_template("not-found.html"), 404 734 735repo = git.Repo(server_repo_location) 736repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 737 738files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r", 739"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1] 740 741print(files) 742 743return flask.render_template( 744"repo-commit.html", 745username=username, 746repository=repository, 747remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 748is_favourite=get_favourite(flask.session.get("username"), username, repository), 749diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff", 750str(sha) + "^!", "--", file).decode().split("\n") for 751file in files}, 752data=db.session.get(Commit, f"/{username}/{repository}/{sha}"), 753) 754 755 756@repositories.route("/<username>/<repository>/forum/") 757def repository_forum(username, repository): 758server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 759if not os.path.exists(server_repo_location): 760app.logger.error(f"Cannot load {server_repo_location}") 761flask.abort(404) 762if not (get_visibility(username, repository) or get_permission_level( 763flask.session.get("username"), username, 764repository) is not None): 765flask.abort(403) 766 767app.logger.info(f"Loading {server_repo_location}") 768 769if not os.path.exists(server_repo_location): 770app.logger.error(f"Cannot load {server_repo_location}") 771return flask.render_template("not-found.html"), 404 772 773repo = git.Repo(server_repo_location) 774repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 775user = User.query.filter_by(username=flask.session.get("username")).first() 776relationships = RepoAccess.query.filter_by(repo=repo_data) 777user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 778 779return flask.render_template( 780"repo-forum.html", 781username=username, 782repository=repository, 783repo_data=repo_data, 784relationships=relationships, 785repo=repo, 786user_relationship=user_relationship, 787Post=Post, 788remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 789is_favourite=get_favourite(flask.session.get("username"), username, repository), 790default_branch=repo_data.default_branch 791) 792 793 794@repositories.route("/<username>/<repository>/forum/topic/<int:id>") 795def repository_forum_topic(username, repository, id): 796server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 797if not os.path.exists(server_repo_location): 798app.logger.error(f"Cannot load {server_repo_location}") 799flask.abort(404) 800if not (get_visibility(username, repository) or get_permission_level( 801flask.session.get("username"), username, 802repository) is not None): 803flask.abort(403) 804 805app.logger.info(f"Loading {server_repo_location}") 806 807if not os.path.exists(server_repo_location): 808app.logger.error(f"Cannot load {server_repo_location}") 809return flask.render_template("not-found.html"), 404 810 811repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 812user = User.query.filter_by(username=flask.session.get("username")).first() 813relationships = RepoAccess.query.filter_by(repo=repo_data) 814user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 815 816post = Post.query.filter_by(id=id).first() 817 818return flask.render_template( 819"repo-topic.html", 820username=username, 821repository=repository, 822repo_data=repo_data, 823relationships=relationships, 824user_relationship=user_relationship, 825post=post, 826remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 827is_favourite=get_favourite(flask.session.get("username"), username, repository), 828default_branch=repo_data.default_branch 829) 830 831 832@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"]) 833def repository_forum_new(username, repository): 834server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 835if not os.path.exists(server_repo_location): 836app.logger.error(f"Cannot load {server_repo_location}") 837flask.abort(404) 838if not (get_visibility(username, repository) or get_permission_level( 839flask.session.get("username"), username, 840repository) is not None): 841flask.abort(403) 842 843app.logger.info(f"Loading {server_repo_location}") 844 845if not os.path.exists(server_repo_location): 846app.logger.error(f"Cannot load {server_repo_location}") 847return flask.render_template("not-found.html"), 404 848 849repo = git.Repo(server_repo_location) 850repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 851user = User.query.filter_by(username=flask.session.get("username")).first() 852relationships = RepoAccess.query.filter_by(repo=repo_data) 853user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 854 855post = Post(user, repo_data, None, flask.request.form["subject"], 856flask.request.form["message"]) 857 858db.session.add(post) 859db.session.commit() 860 861return flask.redirect( 862flask.url_for(".repository_forum_thread", username=username, repository=repository, 863post_id=post.number), 864code=303) 865 866 867@repositories.route("/<username>/<repository>/forum/<int:post_id>") 868def repository_forum_thread(username, repository, post_id): 869server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 870if not os.path.exists(server_repo_location): 871app.logger.error(f"Cannot load {server_repo_location}") 872flask.abort(404) 873if not (get_visibility(username, repository) or get_permission_level( 874flask.session.get("username"), username, 875repository) is not None): 876flask.abort(403) 877 878app.logger.info(f"Loading {server_repo_location}") 879 880if not os.path.exists(server_repo_location): 881app.logger.error(f"Cannot load {server_repo_location}") 882return flask.render_template("not-found.html"), 404 883 884repo = git.Repo(server_repo_location) 885repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 886user = User.query.filter_by(username=flask.session.get("username")).first() 887relationships = RepoAccess.query.filter_by(repo=repo_data) 888user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 889 890return flask.render_template( 891"repo-forum-thread.html", 892username=username, 893repository=repository, 894repo_data=repo_data, 895relationships=relationships, 896repo=repo, 897Post=Post, 898user_relationship=user_relationship, 899post_id=post_id, 900max_post_nesting=4, 901remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 902is_favourite=get_favourite(flask.session.get("username"), username, repository), 903parent=Post.query.filter_by(repo=repo_data, number=post_id).first(), 904has_permission=not ((not get_permission_level(flask.session.get("username"), username, 905repository)) and db.session.get(Post, 906f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")), 907) 908 909 910@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state", 911methods=["POST"]) 912def repository_forum_change_state(username, repository, post_id): 913server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 914if not os.path.exists(server_repo_location): 915app.logger.error(f"Cannot load {server_repo_location}") 916flask.abort(404) 917if (not get_permission_level(flask.session.get("username"), username, repository)) and db.session.get(Post, f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username"): 918flask.abort(403) 919 920app.logger.info(f"Loading {server_repo_location}") 921 922repo = git.Repo(server_repo_location) 923repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 924user = User.query.filter_by(username=flask.session.get("username")).first() 925relationships = RepoAccess.query.filter_by(repo=repo_data) 926user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 927 928post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 929 930if not post: 931flask.abort(404) 932 933post.state = int(flask.request.form["new-state"]) 934 935db.session.commit() 936 937return flask.redirect( 938flask.url_for(".repository_forum_thread", username=username, repository=repository, 939post_id=post_id), 940code=303) 941 942 943@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"]) 944def repository_forum_reply(username, repository, post_id): 945server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 946if not os.path.exists(server_repo_location): 947app.logger.error(f"Cannot load {server_repo_location}") 948flask.abort(404) 949if not (get_visibility(username, repository) or get_permission_level( 950flask.session.get("username"), username, 951repository) is not None): 952flask.abort(403) 953 954app.logger.info(f"Loading {server_repo_location}") 955 956if not os.path.exists(server_repo_location): 957app.logger.error(f"Cannot load {server_repo_location}") 958return flask.render_template("not-found.html"), 404 959 960repo = git.Repo(server_repo_location) 961repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 962user = User.query.filter_by(username=flask.session.get("username")).first() 963relationships = RepoAccess.query.filter_by(repo=repo_data) 964user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 965if not user: 966flask.abort(401) 967 968parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 969post = Post(user, repo_data, parent, flask.request.form["subject"], 970flask.request.form["message"]) 971 972db.session.add(post) 973post.update_date() 974db.session.commit() 975 976return flask.redirect( 977flask.url_for(".repository_forum_thread", username=username, repository=repository, 978post_id=post_id), 979code=303) 980 981 982@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", 983defaults={"score": 1}) 984@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown", 985defaults={"score": -1}) 986@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0}) 987def repository_forum_vote(username, repository, post_id, score): 988server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 989if not os.path.exists(server_repo_location): 990app.logger.error(f"Cannot load {server_repo_location}") 991flask.abort(404) 992if not (get_visibility(username, repository) or get_permission_level( 993flask.session.get("username"), username, 994repository) is not None): 995flask.abort(403) 996 997app.logger.info(f"Loading {server_repo_location}") 998 999if not os.path.exists(server_repo_location): 1000app.logger.error(f"Cannot load {server_repo_location}") 1001return flask.render_template("not-found.html"), 404 1002 1003repo = git.Repo(server_repo_location) 1004repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1005user = User.query.filter_by(username=flask.session.get("username")).first() 1006relationships = RepoAccess.query.filter_by(repo=repo_data) 1007user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1008if not user: 1009flask.abort(401) 1010 1011post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 1012 1013if score: 1014old_relationship = PostVote.query.filter_by(user_username=user.username, 1015post_identifier=post.identifier).first() 1016if old_relationship: 1017if score == old_relationship.vote_score: 1018db.session.delete(old_relationship) 1019post.vote_sum -= old_relationship.vote_score 1020else: 1021post.vote_sum -= old_relationship.vote_score 1022post.vote_sum += score 1023old_relationship.vote_score = score 1024else: 1025relationship = PostVote(user, post, score) 1026post.vote_sum += score 1027db.session.add(relationship) 1028 1029db.session.commit() 1030 1031user_vote = PostVote.query.filter_by(user_username=user.username, 1032post_identifier=post.identifier).first() 1033response = flask.make_response( 1034str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0)) 1035response.content_type = "text/plain" 1036 1037return response 1038 1039 1040@repositories.route("/<username>/<repository>/favourite") 1041def repository_favourite(username, repository): 1042server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1043if not os.path.exists(server_repo_location): 1044app.logger.error(f"Cannot load {server_repo_location}") 1045flask.abort(404) 1046if not (get_visibility(username, repository) or get_permission_level( 1047flask.session.get("username"), username, 1048repository) is not None): 1049flask.abort(403) 1050 1051app.logger.info(f"Loading {server_repo_location}") 1052 1053if not os.path.exists(server_repo_location): 1054app.logger.error(f"Cannot load {server_repo_location}") 1055return flask.render_template("not-found.html"), 404 1056 1057repo = git.Repo(server_repo_location) 1058repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1059user = User.query.filter_by(username=flask.session.get("username")).first() 1060relationships = RepoAccess.query.filter_by(repo=repo_data) 1061user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1062if not user: 1063flask.abort(401) 1064 1065old_relationship = RepoFavourite.query.filter_by(user_username=user.username, 1066repo_route=repo_data.route).first() 1067if old_relationship: 1068db.session.delete(old_relationship) 1069else: 1070relationship = RepoFavourite(user, repo_data) 1071db.session.add(relationship) 1072 1073db.session.commit() 1074 1075return flask.redirect(flask.url_for("favourites"), code=303) 1076 1077 1078@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 1079def repository_users(username, repository): 1080server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1081if not os.path.exists(server_repo_location): 1082app.logger.error(f"Cannot load {server_repo_location}") 1083flask.abort(404) 1084if not (get_visibility(username, repository) or get_permission_level( 1085flask.session.get("username"), username, 1086repository) is not None): 1087flask.abort(403) 1088 1089app.logger.info(f"Loading {server_repo_location}") 1090 1091if not os.path.exists(server_repo_location): 1092app.logger.error(f"Cannot load {server_repo_location}") 1093return flask.render_template("not-found.html"), 404 1094 1095repo = git.Repo(server_repo_location) 1096repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1097user = User.query.filter_by(username=flask.session.get("username")).first() 1098relationships = RepoAccess.query.filter_by(repo=repo_data) 1099user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 1100 1101if flask.request.method == "GET": 1102return flask.render_template( 1103"repo-users.html", 1104username=username, 1105repository=repository, 1106repo_data=repo_data, 1107relationships=relationships, 1108repo=repo, 1109user_relationship=user_relationship, 1110remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1111is_favourite=get_favourite(flask.session.get("username"), username, repository) 1112) 1113else: 1114if get_permission_level(flask.session.get("username"), username, repository) != 2: 1115flask.abort(401) 1116 1117if flask.request.form.get("new-username"): 1118# Create new relationship 1119new_user = User.query.filter_by( 1120username=flask.request.form.get("new-username")).first() 1121relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level")) 1122db.session.add(relationship) 1123db.session.commit() 1124if flask.request.form.get("update-username"): 1125# Create new relationship 1126updated_user = User.query.filter_by( 1127username=flask.request.form.get("update-username")).first() 1128relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first() 1129if flask.request.form.get("update-level") == -1: 1130relationship.delete() 1131else: 1132relationship.access_level = flask.request.form.get("update-level") 1133db.session.commit() 1134 1135return flask.redirect( 1136app.url_for(".repository_users", username=username, repository=repository)) 1137 1138 1139@repositories.route("/<username>/<repository>/branches/") 1140def repository_branches(username, repository): 1141server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1142if not os.path.exists(server_repo_location): 1143app.logger.error(f"Cannot load {server_repo_location}") 1144flask.abort(404) 1145if not (get_visibility(username, repository) or get_permission_level( 1146flask.session.get("username"), username, 1147repository) is not None): 1148flask.abort(403) 1149 1150app.logger.info(f"Loading {server_repo_location}") 1151 1152if not os.path.exists(server_repo_location): 1153app.logger.error(f"Cannot load {server_repo_location}") 1154return flask.render_template("not-found.html"), 404 1155 1156repo = git.Repo(server_repo_location) 1157repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1158 1159return flask.render_template( 1160"repo-branches.html", 1161username=username, 1162repository=repository, 1163repo_data=repo_data, 1164repo=repo, 1165remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1166is_favourite=get_favourite(flask.session.get("username"), username, repository) 1167) 1168 1169 1170@repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 1171@repositories.route("/<username>/<repository>/log/<branch>/") 1172def repository_log(username, repository, branch): 1173server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1174if not os.path.exists(server_repo_location): 1175app.logger.error(f"Cannot load {server_repo_location}") 1176flask.abort(404) 1177if not (get_visibility(username, repository) or get_permission_level( 1178flask.session.get("username"), username, 1179repository) is not None): 1180flask.abort(403) 1181 1182app.logger.info(f"Loading {server_repo_location}") 1183 1184if not os.path.exists(server_repo_location): 1185app.logger.error(f"Cannot load {server_repo_location}") 1186return flask.render_template("not-found.html"), 404 1187 1188repo = git.Repo(server_repo_location) 1189repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1190if not repo_data.default_branch: 1191if repo.heads: 1192repo_data.default_branch = repo.heads[0].name 1193else: 1194return flask.render_template("empty.html", 1195remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 1196if not branch: 1197branch = repo_data.default_branch 1198return flask.redirect(f"./{branch}", code=302) 1199 1200if branch.startswith("tag:"): 1201ref = f"tags/{branch[4:]}" 1202elif branch.startswith("~"): 1203ref = branch[1:] 1204else: 1205ref = f"heads/{branch}" 1206 1207ref = ref.replace("~", "/") # encode slashes for URL support 1208 1209try: 1210repo.git.checkout("-f", ref) 1211except git.exc.GitCommandError: 1212return flask.render_template("not-found.html"), 404 1213 1214branches = repo.heads 1215 1216all_refs = [] 1217for ref in repo.heads: 1218all_refs.append((ref, "head")) 1219for ref in repo.tags: 1220all_refs.append((ref, "tag")) 1221 1222commit_list = [f"/{username}/{repository}/{sha}" for sha in 1223git_command(server_repo_location, None, "log", 1224"--format='%H'").decode().split("\n")] 1225 1226commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc()) 1227page_number = flask.request.args.get("page", 1, type=int) 1228if flask.session.get("username"): 1229default_page_length = db.session.get(User, flask.session.get("username")).default_page_length 1230else: 1231default_page_length = 16 1232page_length = flask.request.args.get("per_page", default_page_length, type=int) 1233page_listing = db.paginate(commits, page=page_number, per_page=page_length) 1234 1235if page_listing.has_next: 1236next_page = page_listing.next_num 1237else: 1238next_page = None 1239 1240if page_listing.has_prev: 1241prev_page = page_listing.prev_num 1242else: 1243prev_page = None 1244 1245return flask.render_template( 1246"repo-log.html", 1247username=username, 1248repository=repository, 1249branches=all_refs, 1250current=branch, 1251repo_data=repo_data, 1252repo=repo, 1253commits=page_listing, 1254remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1255is_favourite=get_favourite(flask.session.get("username"), username, repository), 1256page_number=page_number, 1257page_length=page_length, 1258next_page=next_page, 1259prev_page=prev_page, 1260num_pages=page_listing.pages 1261) 1262 1263 1264@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"]) 1265def repository_prs(username, repository): 1266server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1267if not os.path.exists(server_repo_location): 1268app.logger.error(f"Cannot load {server_repo_location}") 1269flask.abort(404) 1270if not (get_visibility(username, repository) or get_permission_level( 1271flask.session.get("username"), username, 1272repository) is not None): 1273flask.abort(403) 1274 1275app.logger.info(f"Loading {server_repo_location}") 1276 1277if not os.path.exists(server_repo_location): 1278app.logger.error(f"Cannot load {server_repo_location}") 1279return flask.render_template("not-found.html"), 404 1280 1281if flask.request.method == "GET": 1282repo = git.Repo(server_repo_location) 1283repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1284user = User.query.filter_by(username=flask.session.get("username")).first() 1285 1286return flask.render_template( 1287"repo-prs.html", 1288username=username, 1289repository=repository, 1290repo_data=repo_data, 1291repo=repo, 1292PullRequest=PullRequest, 1293remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 1294is_favourite=get_favourite(flask.session.get("username"), username, repository), 1295default_branch=repo_data.default_branch, 1296branches=repo.branches 1297) 1298 1299else: 1300repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1301head = flask.request.form.get("head") 1302head_route = flask.request.form.get("headroute") 1303base = flask.request.form.get("base") 1304 1305if not head and base and head_route: 1306return flask.redirect(".", 400) 1307 1308head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/"))) 1309base_repo = git.Repo(server_repo_location) 1310print(head_repo) 1311 1312if head not in head_repo.branches or base not in base_repo.branches: 1313flask.flash(Markup( 1314"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")), 1315category="error") 1316return flask.redirect(".", 303) 1317 1318head_data = db.session.get(Repo, head_route) 1319if not head_data.visibility: 1320flask.flash(Markup( 1321"<iconify-icon icon='mdi:error'></iconify-icon>" + _( 1322"Head can't be restricted")), 1323category="error") 1324return flask.redirect(".", 303) 1325 1326pull_request = PullRequest(repo_data, head, head_data, base, 1327db.session.get(User, flask.session["username"])) 1328 1329db.session.add(pull_request) 1330db.session.commit() 1331 1332return flask.redirect(".", 303) 1333 1334 1335@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"]) 1336def repository_prs_merge(username, repository): 1337server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1338if not os.path.exists(server_repo_location): 1339app.logger.error(f"Cannot load {server_repo_location}") 1340flask.abort(404) 1341if not (get_visibility(username, repository) or get_permission_level( 1342flask.session.get("username"), username, 1343repository) is not None): 1344flask.abort(403) 1345 1346if not get_permission_level(flask.session.get("username"), username, repository): 1347flask.abort(401) 1348 1349repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1350repo = git.Repo(server_repo_location) 1351id = flask.request.form.get("id") 1352 1353pull_request = db.session.get(PullRequest, id) 1354 1355if pull_request: 1356result = celery_tasks.merge_heads.delay( 1357pull_request.head_route, 1358pull_request.head_branch, 1359pull_request.base_route, 1360pull_request.base_branch, 1361simulate=True 1362) 1363task_result = worker.AsyncResult(result.id) 1364 1365return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1366# db.session.delete(pull_request) 1367# db.session.commit() 1368else: 1369flask.abort(400) 1370 1371 1372@repositories.route("/<username>/<repository>/prs/<int:id>/merge") 1373def repository_prs_merge_stage_two(username, repository, id): 1374server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1375if not os.path.exists(server_repo_location): 1376app.logger.error(f"Cannot load {server_repo_location}") 1377flask.abort(404) 1378if not (get_visibility(username, repository) or get_permission_level( 1379flask.session.get("username"), username, 1380repository) is not None): 1381flask.abort(403) 1382 1383if not get_permission_level(flask.session.get("username"), username, repository): 1384flask.abort(401) 1385 1386repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1387repo = git.Repo(server_repo_location) 1388 1389pull_request = db.session.get(PullRequest, id) 1390 1391if pull_request: 1392result = celery_tasks.merge_heads.delay( 1393pull_request.head_route, 1394pull_request.head_branch, 1395pull_request.base_route, 1396pull_request.base_branch, 1397simulate=False 1398) 1399task_result = worker.AsyncResult(result.id) 1400 1401pull_request.state = 1 1402db.session.commit() 1403 1404return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 1405# db.session.delete(pull_request) 1406else: 1407flask.abort(400) 1408 1409 1410@app.route("/task/<task_id>") 1411def task_monitor(task_id): 1412task_result = worker.AsyncResult(task_id) 1413print(task_result.status) 1414 1415return flask.render_template("task-monitor.html", result=task_result) 1416 1417 1418@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"]) 1419def repository_prs_delete(username, repository): 1420server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 1421if not os.path.exists(server_repo_location): 1422app.logger.error(f"Cannot load {server_repo_location}") 1423flask.abort(404) 1424if not (get_visibility(username, repository) or get_permission_level( 1425flask.session.get("username"), username, 1426repository) is not None): 1427flask.abort(403) 1428 1429if not get_permission_level(flask.session.get("username"), username, repository): 1430flask.abort(401) 1431 1432repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 1433repo = git.Repo(server_repo_location) 1434id = flask.request.form.get("id") 1435 1436pull_request = db.session.get(PullRequest, id) 1437 1438if pull_request: 1439pull_request.state = 2 1440db.session.commit() 1441 1442return flask.redirect(".", 303) 1443 1444 1445@repositories.route("/<username>/<repository>/settings/") 1446def repository_settings(username, repository): 1447if get_permission_level(flask.session.get("username"), username, repository) != 2: 1448flask.abort(401) 1449 1450return flask.render_template("repo-settings.html", username=username, repository=repository) 1451 1452 1453@app.errorhandler(404) 1454def e404(error): 1455return flask.render_template("not-found.html"), 404 1456 1457 1458@app.errorhandler(401) 1459def e401(error): 1460return flask.render_template("unauthorised.html"), 401 1461 1462 1463@app.errorhandler(403) 1464def e403(error): 1465return flask.render_template("forbidden.html"), 403 1466 1467 1468@app.errorhandler(418) 1469def e418(error): 1470return flask.render_template("teapot.html"), 418 1471 1472 1473@app.errorhandler(405) 1474def e405(error): 1475return flask.render_template("method-not-allowed.html"), 405 1476 1477 1478if __name__ == "__main__": 1479app.run(debug=True, port=8080, host="0.0.0.0") 1480 1481app.register_blueprint(repositories) 1482