Python script, Unicode text, UTF-8 text executable
        
            1
            __version__ = "0.5.0" 
        
            2
            import os 
        
            4
            import shutil 
        
            5
            import random 
        
            6
            import subprocess 
        
            7
            import platform 
        
            8
            import PIL 
        
            10
            import git 
        
            11
            import mimetypes 
        
            12
            import magic 
        
            13
            import flask 
        
            14
            import cairosvg 
        
            15
            import celery 
        
            16
            import shlex 
        
            17
            from functools import wraps 
        
            18
            from datetime import datetime 
        
            19
            from enum import Enum 
        
            20
            from cairosvg import svg2png 
        
            21
            from flask_sqlalchemy import SQLAlchemy 
        
            22
            from flask_bcrypt import Bcrypt 
        
            23
            from markupsafe import escape, Markup 
        
            24
            from flask_migrate import Migrate 
        
            25
            from PIL import Image 
        
            26
            from flask_httpauth import HTTPBasicAuth 
        
            27
            import config 
        
            28
            import markdown 
        
            29
            from common import git_command 
        
            30
            from flask_babel import Babel, gettext, ngettext, force_locale 
        
            31
            from jinja2_fragments.flask import render_block 
        
            32
            import logging 
        
            34
            class No304(logging.Filter): 
        
            37
                def filter(self, record): 
        
            38
                    return not record.getMessage().strip().endswith("304 -") 
        
            39
            logging.getLogger("werkzeug").addFilter(No304()) 
        
            42
            _ = gettext 
        
            44
            n_ = ngettext 
        
            45
            app = flask.Flask(__name__) 
        
            47
            app.config.from_mapping( 
        
            48
                    CELERY=dict( 
        
            49
                            broker_url=config.REDIS_URI, 
        
            50
                            result_backend=config.REDIS_URI, 
        
            51
                            task_ignore_result=True, 
        
            52
                    ), 
        
            53
            ) 
        
            54
            auth = HTTPBasicAuth() 
        
            56
            app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 
        
            58
            app.config["SECRET_KEY"] = config.DB_PASSWORD 
        
            59
            app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 
        
            60
            app.config["BABEL_TRANSLATION_DIRECTORIES"] = "i18n" 
        
            61
            app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE 
        
            62
            app.config["SESSION_COOKIE_SAMESITE"] = "Lax" 
        
            63
            app.config["SESSION_COOKIE_SECURE"] = config.suggest_https       # only send cookies over HTTPS if the server is configured for it 
        
            64
            app.config["SESSION_COOKIE_HTTPONLY"] = True                     # don't allow JS to access the cookie 
        
            65
            if config.restrict_cookie_domain: 
        
            66
                app.config["SESSION_COOKIE_DOMAIN"] = config.BASE_DOMAIN         # don't share across subdomains, since user content is hosted there 
        
            67
            db = SQLAlchemy(app) 
        
            69
            bcrypt = Bcrypt(app) 
        
            70
            migrate = Migrate(app, db) 
        
            71
            from misc_utils import * 
        
            73
            import git_http 
        
            75
            import api 
        
            76
            import jinja_utils 
        
            77
            import celery_tasks 
        
            78
            from celery import Celery, Task 
        
            79
            import celery_integration 
        
            80
            import pathlib 
        
            81
            from models import * 
        
            83
            babel = Babel(app) 
        
            85
            def get_locale(): 
        
            88
                if flask.request.cookies.get("language"): 
        
            89
                    return flask.request.cookies.get("language") 
        
            90
                return flask.request.accept_languages.best_match(config.available_locales) 
        
            91
            babel.init_app(app, locale_selector=get_locale) 
        
            94
            with app.app_context(): 
        
            96
                locale_names = {} 
        
            97
                for language in config.available_locales: 
        
            98
                    with force_locale(language): 
        
            99
                        # NOTE: Translate this to the language's name in that language, for example in French you would use français 
        
            100
                        locale_names[language] = gettext("English") 
        
            101
            worker = celery_integration.init_celery_app(app) 
        
            103
            repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 
        
            105
            app.jinja_env.add_extension("jinja2.ext.do") 
        
            107
            app.jinja_env.add_extension("jinja2.ext.loopcontrols") 
        
            108
            app.jinja_env.add_extension("jinja2.ext.debug") 
        
            109
            @app.context_processor 
        
            112
            def default(): 
        
            113
                username = flask.session.get("username") 
        
            114
                user_object = User.query.filter_by(username=username).first() 
        
            116
                return { 
        
            118
                    "logged_in_user": username, 
        
            119
                    "user_object": user_object, 
        
            120
                    "Notification": Notification, 
        
            121
                    "unread": UserNotification.query.filter_by(user_username=username).filter( 
        
            122
                            UserNotification.attention_level > 0).count(), 
        
            123
                    "config": config, 
        
            124
                    "Markup": Markup, 
        
            125
                    "locale_names": locale_names, 
        
            126
                    "set": set,          # since using {} is impossible in Jinja 
        
            127
                    "request": flask.request, 
        
            128
                    "get_visibility": get_visibility, 
        
            129
                    "get_permission_level": get_permission_level, 
        
            130
                } 
        
            131
            @app.route("/") 
        
            134
            def main(): 
        
            135
                if flask.session.get("username"): 
        
            136
                    return flask.render_template("home.html") 
        
            137
                else: 
        
            138
                    return flask.render_template("no-home.html") 
        
            139
            @app.route("/userstyle") 
        
            142
            def userstyle(): 
        
            143
                if flask.session.get("username") and os.path.exists( 
        
            144
                        os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config", 
        
            145
                                     "theme.css")): 
        
            146
                    return flask.send_from_directory( 
        
            147
                        os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config"), 
        
            148
                        "theme.css") 
        
            149
                else: 
        
            150
                    return flask.Response("", mimetype="text/css") 
        
            151
            @app.route("/about/") 
        
            154
            def about(): 
        
            155
                return flask.render_template("about.html", platform=platform, version=__version__) 
        
            156
            @app.route("/search") 
        
            159
            def search(): 
        
            160
                query = flask.request.args.get("q") 
        
            161
                page_number = flask.request.args.get("page", 1, type=int) 
        
            162
                if flask.session.get("username"): 
        
            163
                    default_page_length = db.session.get(User, flask.session.get("username")).default_page_length 
        
            164
                else: 
        
            165
                    default_page_length = 16 
        
            166
                page_length = flask.request.args.get("per_page", default_page_length, type=int) 
        
            167
                if not query: 
        
            168
                    flask.abort(400) 
        
            169
                results = Repo.query.filter(Repo.name.ilike(f"%{query}%")).filter_by(visibility=2).paginate( 
        
            171
                        page=page_number, per_page=page_length) 
        
            172
                if results.has_next: 
        
            174
                    next_page = results.next_num 
        
            175
                else: 
        
            176
                    next_page = None 
        
            177
                if results.has_prev: 
        
            179
                    prev_page = results.prev_num 
        
            180
                else: 
        
            181
                    prev_page = None 
        
            182
                return flask.render_template("search.html", results=results, query=query, 
        
            184
                                             page_number=page_number, 
        
            185
                                             page_length=page_length, 
        
            186
                                             next_page=next_page, 
        
            187
                                             prev_page=prev_page, 
        
            188
                                             num_pages=results.pages) 
        
            189
            @app.route("/language", methods=["POST"]) 
        
            192
            def set_locale(): 
        
            193
                response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 
        
            194
                                          code=303) 
        
            195
                if not flask.request.form.get("language"): 
        
            196
                    response.delete_cookie("language") 
        
            197
                else: 
        
            198
                    response.set_cookie("language", flask.request.form.get("language")) 
        
            199
                return response 
        
            201
            @app.route("/cookie-dismiss") 
        
            204
            def dismiss_banner(): 
        
            205
                response = flask.redirect(flask.request.referrer if flask.request.referrer else "/", 
        
            206
                                          code=303) 
        
            207
                response.set_cookie("cookie-banner", "1") 
        
            208
                return response 
        
            209
            @app.route("/help/") 
        
            212
            def help_redirect(): 
        
            213
                return flask.redirect(config.help_url, code=302) 
        
            214
            @app.route("/settings/") 
        
            217
            def settings(): 
        
            218
                if not flask.session.get("username"): 
        
            219
                    flask.abort(401) 
        
            220
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            221
                return flask.render_template("user-settings.html", user=user) 
        
            223
            @app.route("/settings/confirm-email/<code>") 
        
            226
            def confirm_email(code): 
        
            227
                request = EmailChangeRequest.query.filter_by(code=code).first() 
        
            228
                if not request: 
        
            229
                    flask.abort(404) 
        
            230
                user = db.session.get(User, request.user_username) 
        
            232
                user.email = request.new_email 
        
            233
                db.session.delete(request) 
        
            234
                db.session.commit() 
        
            235
                return flask.redirect("/settings", code=303) 
        
            237
            @app.route("/settings/profile", methods=["POST"]) 
        
            240
            def settings_profile(): 
        
            241
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            242
                user.display_name = flask.request.form["displayname"] 
        
            244
                user.URL = flask.request.form["url"] 
        
            245
                user.company = flask.request.form["company"] 
        
            246
                user.company_URL = flask.request.form["companyurl"] 
        
            247
                if not flask.request.form.get("email"): 
        
            248
                    # Deleting the email can be instant; no need to confirm 
        
            249
                    user.email = "" 
        
            250
                elif flask.request.form.get("email") != user.email: 
        
            251
                    # Changing the email requires confirmation from the address holder 
        
            252
                    celery_tasks.request_email_change.delay(user.username, flask.request.form["email"]) 
        
            253
                user.location = flask.request.form["location"] 
        
            254
                user.show_mail = True if flask.request.form.get("showmail") else False 
        
            255
                user.bio = flask.request.form.get("bio") 
        
            256
                db.session.commit() 
        
            258
                flask.flash( 
        
            260
                        Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), 
        
            261
                        category="success") 
        
            262
                return flask.redirect(f"/{flask.session.get('username')}", code=303) 
        
            263
            @app.route("/settings/preferences", methods=["POST"]) 
        
            266
            def settings_prefs(): 
        
            267
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            268
                user.default_page_length = int(flask.request.form["page_length"]) 
        
            270
                user.max_post_nesting = int(flask.request.form["max_post_nesting"]) 
        
            271
                db.session.commit() 
        
            273
                flask.flash( 
        
            275
                        Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")), 
        
            276
                        category="success") 
        
            277
                return flask.redirect(f"/{flask.session.get('username')}", code=303) 
        
            278
            @app.route("/favourites/", methods=["GET", "POST"]) 
        
            281
            def favourites(): 
        
            282
                if not flask.session.get("username"): 
        
            283
                    flask.abort(401) 
        
            284
                if flask.request.method == "GET": 
        
            285
                    relationships = RepoFavourite.query.filter_by( 
        
            286
                            user_username=flask.session.get("username")) 
        
            287
                    return flask.render_template("favourites.html", favourites=relationships) 
        
            289
            @app.route("/favourites/<int:id>", methods=["POST"]) 
        
            292
            def favourite_edit(id): 
        
            293
                if not flask.session.get("username"): 
        
            294
                    flask.abort(401) 
        
            295
                favourite = db.session.get(RepoFavourite, id) 
        
            296
                if favourite.user_username != flask.session.get("username"): 
        
            297
                    flask.abort(403) 
        
            298
                data = flask.request.form 
        
            299
                favourite.notify_commit = js_to_bool(data.get("commit")) 
        
            300
                favourite.notify_forum = js_to_bool(data.get("forum")) 
        
            301
                favourite.notify_pr = js_to_bool(data.get("pull_request")) 
        
            302
                favourite.notify_admin = js_to_bool(data.get("administrative")) 
        
            303
                db.session.commit() 
        
            304
                return flask.render_template_string( 
        
            305
                        """ 
        
            306
                    <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"> 
        
            307
                        <td><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></td> 
        
            308
                        <td style="text-align: center;"><input type="checkbox" name="commit" id="commit-{{ favourite.id }}" value="true" {% if favourite.notify_commit %}checked{% endif %}></td> 
        
            309
                        <td style="text-align: center;"><input type="checkbox" name="forum" id="forum-{{ favourite.id }}" value="true" {% if favourite.notify_forum %}checked{% endif %}></td> 
        
            310
                        <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> 
        
            311
                        <td style="text-align: center;"><input type="checkbox" name="administrative" id="administrative-{{ favourite.id }}" value="true" {% if favourite.notify_admin %}checked{% endif %}></td> 
        
            312
                    </tr> 
        
            313
                    """, 
        
            314
                        favourite=favourite 
        
            315
                ) 
        
            316
            @app.route("/notifications/", methods=["GET", "POST"]) 
        
            319
            def notifications(): 
        
            320
                if not flask.session.get("username"): 
        
            321
                    flask.abort(401) 
        
            322
                if flask.request.method == "GET": 
        
            323
                    page_number = flask.request.args.get("page", 1, type=int) 
        
            324
                    if flask.session.get("username"): 
        
            325
                        default_page_length = db.session.get(User, flask.session.get( 
        
            326
                            "username")).default_page_length 
        
            327
                    else: 
        
            328
                        default_page_length = 16 
        
            329
                    page_length = flask.request.args.get("per_page", default_page_length, type=int) 
        
            330
                    results = UserNotification.query.filter_by( 
        
            332
                            user_username=flask.session.get("username")).order_by(UserNotification.id.desc()).paginate( 
        
            333
                            page=page_number, per_page=page_length) 
        
            334
                    if results.has_next: 
        
            336
                        next_page = results.next_num 
        
            337
                    else: 
        
            338
                        next_page = None 
        
            339
                    if results.has_prev: 
        
            341
                        prev_page = results.prev_num 
        
            342
                    else: 
        
            343
                        prev_page = None 
        
            344
                    return flask.render_template("notifications.html", 
        
            346
                                                 notifications=results, 
        
            347
                                                 db=db, Commit=Commit, Post=Post, PullRequest=PullRequest, User=User, 
        
            348
                                                 page_number=page_number, 
        
            349
                                                 page_length=page_length, 
        
            350
                                                 next_page=next_page, 
        
            351
                                                 prev_page=prev_page, 
        
            352
                                                 num_pages=results.pages 
        
            353
                    ) 
        
            354
            @app.route("/notifications/<int:notification_id>/read", methods=["POST"]) 
        
            357
            def mark_read(notification_id): 
        
            358
                if not flask.session.get("username"): 
        
            359
                    flask.abort(401) 
        
            360
                notification = UserNotification.query.filter_by(id=notification_id).first() 
        
            361
                if notification.user_username != flask.session.get("username"): 
        
            362
                    flask.abort(403) 
        
            363
                notification.mark_read() 
        
            364
                db.session.commit() 
        
            365
                return flask.render_template_string( 
        
            366
                    "<button hx-post='/notifications/{{ notification.id }}/unread' hx-swap='outerHTML'>Mark as unread</button>", 
        
            367
                    notification=notification), 200 
        
            368
            @app.route("/notifications/<int:notification_id>/unread", methods=["POST"]) 
        
            371
            def mark_unread(notification_id): 
        
            372
                if not flask.session.get("username"): 
        
            373
                    flask.abort(401) 
        
            374
                notification = UserNotification.query.filter_by(id=notification_id).first() 
        
            375
                if notification.user_username != flask.session.get("username"): 
        
            376
                    flask.abort(403) 
        
            377
                notification.mark_unread() 
        
            378
                db.session.commit() 
        
            379
                return flask.render_template_string( 
        
            380
                    "<button hx-post='/notifications/{{ notification.id }}/read' hx-swap='outerHTML'>Mark as read</button>", 
        
            381
                    notification=notification), 200 
        
            382
            @app.route("/notifications/mark-all-read", methods=["POST"]) 
        
            385
            def mark_all_read(): 
        
            386
                if not flask.session.get("username"): 
        
            387
                    flask.abort(401) 
        
            388
                notifications = UserNotification.query.filter_by( 
        
            390
                        user_username=flask.session.get("username")) 
        
            391
                for notification in notifications: 
        
            392
                    notification.mark_read() 
        
            393
                db.session.commit() 
        
            394
                return flask.redirect("/notifications/", code=303) 
        
            395
            @app.route("/accounts/", methods=["GET", "POST"]) 
        
            398
            def login(): 
        
            399
                if flask.request.method == "GET": 
        
            400
                    return flask.render_template("login.html") 
        
            401
                else: 
        
            402
                    if "login" in flask.request.form: 
        
            403
                        username = flask.request.form["username"] 
        
            404
                        password = flask.request.form["password"] 
        
            405
                        user = User.query.filter_by(username=username).first() 
        
            407
                        if user and bcrypt.check_password_hash(user.password_hashed, password): 
        
            409
                            flask.session["username"] = user.username 
        
            410
                            flask.flash( 
        
            411
                                    Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _( 
        
            412
                                            "Successfully logged in as {username}").format( 
        
            413
                                        username=username)), 
        
            414
                                    category="success") 
        
            415
                            return flask.redirect("/", code=303) 
        
            416
                        elif not user: 
        
            417
                            flask.flash(Markup( 
        
            418
                                    "<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 
        
            419
                                            "User not found")), 
        
            420
                                    category="alert") 
        
            421
                            return flask.render_template("login.html") 
        
            422
                        else: 
        
            423
                            flask.flash(Markup( 
        
            424
                                    "<iconify-icon icon='mdi:account-question'></iconify-icon>" + _( 
        
            425
                                            "Invalid password")), 
        
            426
                                    category="error") 
        
            427
                            return flask.render_template("login.html") 
        
            428
                    if "signup" in flask.request.form: 
        
            429
                        username = flask.request.form["username"] 
        
            430
                        password = flask.request.form["password"] 
        
            431
                        password2 = flask.request.form["password2"] 
        
            432
                        email = flask.request.form.get("email") 
        
            433
                        email2 = flask.request.form.get("email2")  # repeat email is a honeypot 
        
            434
                        name = flask.request.form.get("name") 
        
            435
                        if not only_chars(username, 
        
            437
                                          "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"): 
        
            438
                            flask.flash(Markup( 
        
            439
                                    _("Usernames may only contain Latin alphabet, numbers and '-'")), 
        
            440
                                    category="error") 
        
            441
                            return flask.render_template("login.html") 
        
            442
                        if "--" in username: 
        
            443
                            flask.flash(Markup( 
        
            444
                                    _("Usernames may not contain consecutive hyphens")), 
        
            445
                                    category="error") 
        
            446
                            return flask.render_template("login.html") 
        
            447
                        if username.startswith("-") or username.endswith("-"): 
        
            448
                            flask.flash(Markup( 
        
            449
                                    _("Usernames may not start or end with a hyphen")), 
        
            450
                                    category="error") 
        
            451
                            return flask.render_template("login.html") 
        
            452
                        if username in config.RESERVED_NAMES: 
        
            453
                            flask.flash( 
        
            454
                                    Markup( 
        
            455
                                            _("Sorry, {username} is a system path").format( 
        
            456
                                                    username=username)), 
        
            457
                                    category="error") 
        
            458
                            return flask.render_template("login.html") 
        
            459
                        if not username.islower(): 
        
            461
                            if not name:           # infer display name from the wanted username if not customised 
        
            462
                                display_name = username 
        
            463
                            username = username.lower() 
        
            464
                            flask.flash(Markup( 
        
            465
                                    _("Usernames must be lowercase, so it's been converted automatically")), 
        
            466
                                    category="info") 
        
            467
                        user_check = User.query.filter_by(username=username).first() 
        
            469
                        if user_check or email2:   # make the honeypot look like a normal error 
        
            470
                            flask.flash( 
        
            471
                                    Markup( 
        
            472
                                            _( 
        
            473
                                                    "The username {username} is taken").format( 
        
            474
                                                    username=username)), 
        
            475
                                    category="error") 
        
            476
                            return flask.render_template("login.html") 
        
            477
                        if password2 != password: 
        
            479
                            flask.flash(Markup(_( 
        
            480
                                    "Make sure the passwords match")), 
        
            481
                                        category="error") 
        
            482
                            return flask.render_template("login.html") 
        
            483
                        user = User(username, password, email, name) 
        
            485
                        db.session.add(user) 
        
            486
                        db.session.commit() 
        
            487
                        flask.session["username"] = user.username 
        
            488
                        flask.flash(Markup( 
        
            489
                                        _( 
        
            490
                                            "Successfully created and logged in as {username}").format( 
        
            491
                                            username=username)), 
        
            492
                                    category="success") 
        
            493
                        notification = Notification({"type": "welcome"}) 
        
            495
                        db.session.add(notification) 
        
            496
                        db.session.commit() 
        
            497
                        return flask.redirect("/", code=303) 
        
            499
            @app.route("/newrepo/", methods=["GET", "POST"]) 
        
            502
            def new_repo(): 
        
            503
                if not flask.session.get("username"): 
        
            504
                    flask.abort(401) 
        
            505
                if flask.request.method == "GET": 
        
            506
                    return flask.render_template("new-repo.html") 
        
            507
                else: 
        
            508
                    name = flask.request.form["name"] 
        
            509
                    visibility = int(flask.request.form["visibility"]) 
        
            510
                    if not only_chars(name, 
        
            512
                                      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_."): 
        
            513
                        flask.flash(Markup( 
        
            514
                                "<iconify-icon icon='mdi:error'></iconify-icon>" + _( 
        
            515
                                        "Repository names may only contain Latin alphabet, numbers, '-', '_' and '.'")), 
        
            516
                                category="error") 
        
            517
                        return flask.render_template("new-repo.html") 
        
            518
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            520
                    repo = Repo(user, name, visibility) 
        
            522
                    db.session.add(repo) 
        
            523
                    db.session.commit() 
        
            524
                    flask.flash(Markup(_("Successfully created repository {name}").format(name=name)), 
        
            526
                                category="success") 
        
            527
                    return flask.redirect(repo.route, code=303) 
        
            528
            @app.route("/logout") 
        
            531
            def logout(): 
        
            532
                flask.session.clear() 
        
            533
                flask.flash(Markup( 
        
            534
                        "<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")), 
        
            535
                        category="info") 
        
            536
                return flask.redirect("/", code=303) 
        
            537
            @app.route("/<username>/", methods=["GET", "POST"]) 
        
            540
            def user_profile(username): 
        
            541
                if db.session.get(User, username) is None: 
        
            542
                    flask.abort(404) 
        
            543
                old_relationship = UserFollow.query.filter_by( 
        
            544
                        follower_username=flask.session.get("username"), 
        
            545
                        followed_username=username).first() 
        
            546
                if flask.request.method == "GET": 
        
            547
                    user = User.query.filter_by(username=username).first() 
        
            548
                    match flask.request.args.get("action"): 
        
            549
                        case "repositories": 
        
            550
                            repos = Repo.query.filter_by(owner_name=username, visibility=2) 
        
            551
                            return flask.render_template("user-profile-repositories.html", user=user, 
        
            552
                                                         repos=repos, 
        
            553
                                                         relationship=old_relationship) 
        
            554
                        case "followers": 
        
            555
                            return flask.render_template("user-profile-followers.html", user=user, 
        
            556
                                                         relationship=old_relationship) 
        
            557
                        case "follows": 
        
            558
                            return flask.render_template("user-profile-follows.html", user=user, 
        
            559
                                                         relationship=old_relationship) 
        
            560
                        case _: 
        
            561
                            return flask.render_template("user-profile-overview.html", user=user, 
        
            562
                                                         relationship=old_relationship) 
        
            563
                elif flask.request.method == "POST": 
        
            565
                    match flask.request.args.get("action"): 
        
            566
                        case "follow": 
        
            567
                            if username == flask.session.get("username"): 
        
            568
                                flask.abort(403) 
        
            569
                            if old_relationship: 
        
            570
                                db.session.delete(old_relationship) 
        
            571
                            else: 
        
            572
                                relationship = UserFollow( 
        
            573
                                        flask.session.get("username"), 
        
            574
                                        username 
        
            575
                                ) 
        
            576
                                print(f"Following {username}") 
        
            577
                                db.session.add(relationship) 
        
            578
                                user = db.session.get(User, username) 
        
            580
                                author = db.session.get(User, flask.session.get("username")) 
        
            581
                                notification = Notification({"type": "follow", "author": author.username, "user": user.username}) 
        
            582
                                db.session.add(notification) 
        
            583
                                db.session.commit() 
        
            584
                                user_notification = UserNotification(user, notification, 1) 
        
            585
                                db.session.add(user_notification) 
        
            586
                                db.session.commit() 
        
            587
                            db.session.commit() 
        
            589
                            return flask.redirect("?", code=303) 
        
            590
            @app.route("/<username>/<repository>/") 
        
            593
            def repository_index(username, repository): 
        
            594
                return flask.redirect("./tree", code=302) 
        
            595
            @app.route("/info/<username>/avatar") 
        
            598
            def user_avatar(username): 
        
            599
                server_userdata_location = os.path.join(config.USERDATA_PATH, username) 
        
            600
                if not os.path.exists(server_userdata_location): 
        
            601
                    return flask.render_template("errors/not-found.html"), 404 
        
            602
                return flask.send_from_directory(server_userdata_location, "avatar.png") 
        
            604
            @app.route("/info/<username>/avatar", methods=["POST"]) 
        
            607
            def user_avatar_upload(username): 
        
            608
                server_userdata_location = os.path.join(config.USERDATA_PATH, username) 
        
            609
                if not os.path.exists(server_userdata_location): 
        
            611
                    flask.abort(404) 
        
            612
                if not flask.session.get("username") == username: 
        
            613
                    flask.abort(403) 
        
            614
                # Convert image to PNG 
        
            616
                try: 
        
            617
                    image = Image.open(flask.request.files["avatar"]) 
        
            618
                except PIL.UnidentifiedImageError: 
        
            619
                    flask.abort(400) 
        
            620
                image.save(os.path.join(server_userdata_location, "avatar.png")) 
        
            621
                return flask.redirect(f"/{username}", code=303) 
        
            623
            @app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 
        
            626
            def repository_raw(username, repository, branch, subpath): 
        
            627
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            628
                if not os.path.exists(server_repo_location): 
        
            629
                    flask.abort(404) 
        
            630
                if not (get_visibility(username, repository) or get_permission_level( 
        
            631
                        flask.session.get("username"), username, 
        
            632
                        repository) is not None): 
        
            633
                    flask.abort(403) 
        
            634
                if not os.path.exists(server_repo_location): 
        
            636
                    return flask.render_template("errors/not-found.html"), 404 
        
            637
                repo = git.Repo(server_repo_location) 
        
            639
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            640
                if not repo_data.default_branch: 
        
            641
                    if repo.heads: 
        
            642
                        repo_data.default_branch = repo.heads[0].name 
        
            643
                    else: 
        
            644
                        return flask.render_template("empty.html", 
        
            645
                                                     remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            646
                if not branch: 
        
            647
                    branch = repo_data.default_branch 
        
            648
                    return flask.redirect(f"./{branch}", code=302) 
        
            649
                if branch.startswith("tag:"): 
        
            651
                    ref = f"tags/{branch[4:]}" 
        
            652
                elif branch.startswith("~"): 
        
            653
                    ref = branch[1:] 
        
            654
                else: 
        
            655
                    ref = f"heads/{branch}" 
        
            656
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            658
                try: 
        
            660
                    repo.git.checkout("-f", ref) 
        
            661
                except git.exc.GitCommandError: 
        
            662
                    return flask.render_template("errors/not-found.html"), 404 
        
            663
                return flask.send_from_directory(config.REPOS_PATH, 
        
            665
                                                 os.path.join(username, repository, subpath)) 
        
            666
            @repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 
        
            669
            @repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 
        
            670
            @repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 
        
            671
            def repository_tree(username, repository, branch, subpath): 
        
            672
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            673
                if not os.path.exists(server_repo_location): 
        
            674
                    flask.abort(404) 
        
            675
                if not (get_visibility(username, repository) or get_permission_level( 
        
            676
                        flask.session.get("username"), username, 
        
            677
                        repository) is not None): 
        
            678
                    flask.abort(403) 
        
            679
                repo = git.Repo(server_repo_location) 
        
            681
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            682
                if not repo_data.default_branch: 
        
            683
                    if repo.heads: 
        
            684
                        repo_data.default_branch = repo.heads[0].name 
        
            685
                    else: 
        
            686
                        return flask.render_template("empty.html", 
        
            687
                                                     remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            688
                if not branch: 
        
            689
                    branch = repo_data.default_branch 
        
            690
                    return flask.redirect(f"./{branch}", code=302) 
        
            691
                if branch.startswith("tag:"): 
        
            693
                    ref = f"tags/{branch[4:]}" 
        
            694
                elif branch.startswith("~"): 
        
            695
                    ref = branch[1:] 
        
            696
                else: 
        
            697
                    ref = f"heads/{branch}" 
        
            698
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            700
                try: 
        
            702
                    repo.git.checkout("-f", ref) 
        
            703
                except git.exc.GitCommandError: 
        
            704
                    return flask.render_template("errors/not-found.html"), 404 
        
            705
                branches = repo.heads 
        
            707
                all_refs = [] 
        
            709
                for ref in repo.heads: 
        
            710
                    all_refs.append((ref, "head")) 
        
            711
                for ref in repo.tags: 
        
            712
                    all_refs.append((ref, "tag")) 
        
            713
                if os.path.isdir(os.path.join(server_repo_location, subpath)): 
        
            715
                    files = [] 
        
            716
                    blobs = [] 
        
            717
                    for entry in os.listdir(os.path.join(server_repo_location, subpath)): 
        
            719
                        if not os.path.basename(entry) == ".git": 
        
            720
                            files.append(os.path.join(subpath, entry)) 
        
            721
                    infos = [] 
        
            723
                    for file in files: 
        
            725
                        path = os.path.join(server_repo_location, file) 
        
            726
                        mimetype = guess_mime(path) 
        
            727
                        text = git_command(server_repo_location, None, "log", "--format='%H\n'", 
        
            729
                                           shlex.quote(file)).decode() 
        
            730
                        sha = text.split("\n")[0] 
        
            732
                        identifier = f"/{username}/{repository}/{sha}" 
        
            733
                        last_commit = db.session.get(Commit, identifier) 
        
            735
                        info = { 
        
            737
                            "name": os.path.basename(file), 
        
            738
                            "serverPath": path, 
        
            739
                            "relativePath": file, 
        
            740
                            "link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 
        
            741
                            "size": human_size(os.path.getsize(path)), 
        
            742
                            "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 
        
            743
                            "commit": last_commit, 
        
            744
                            "shaSize": 7, 
        
            745
                        } 
        
            746
                        special_icon = config.match_icon(os.path.basename(file)) 
        
            748
                        if special_icon: 
        
            749
                            info["icon"] = special_icon 
        
            750
                        elif os.path.isdir(path): 
        
            751
                            info["icon"] = config.folder_icon 
        
            752
                            info["size"] = _("{} files").format(len(os.listdir(path))) 
        
            753
                        elif mimetypes.guess_type(path)[0] in config.file_icons: 
        
            754
                            info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]] 
        
            755
                        else: 
        
            756
                            info["icon"] = config.unknown_icon 
        
            757
                        if os.path.isdir(path): 
        
            759
                            infos.insert(0, info) 
        
            760
                        else: 
        
            761
                            infos.append(info) 
        
            762
                    return flask.render_template( 
        
            764
                            "repo-tree.html", 
        
            765
                            username=username, 
        
            766
                            repository=repository, 
        
            767
                            files=infos, 
        
            768
                            subpath=os.path.join("/", subpath), 
        
            769
                            branches=all_refs, 
        
            770
                            current=branch, 
        
            771
                            remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            772
                            is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            773
                            repo_data=repo_data, 
        
            774
                    ) 
        
            775
                else: 
        
            776
                    path = os.path.join(server_repo_location, subpath) 
        
            777
                    if not os.path.exists(path): 
        
            779
                        return flask.render_template("errors/not-found.html"), 404 
        
            780
                    mimetype = guess_mime(path) 
        
            782
                    mode = mimetype.split("/", 1)[0] 
        
            783
                    size = human_size(os.path.getsize(path)) 
        
            784
                    special_icon = config.match_icon(os.path.basename(path)) 
        
            786
                    if special_icon: 
        
            787
                        icon = special_icon 
        
            788
                    elif os.path.isdir(path): 
        
            789
                        icon = config.folder_icon 
        
            790
                    elif mimetypes.guess_type(path)[0] in config.file_icons: 
        
            791
                        icon = config.file_icons[mimetypes.guess_type(path)[0]] 
        
            792
                    else: 
        
            793
                        icon = config.unknown_icon 
        
            794
                    contents = None 
        
            796
                    if mode == "text": 
        
            797
                        contents = convert_to_html(path) 
        
            798
                    return flask.render_template( 
        
            800
                            "repo-file.html", 
        
            801
                            username=username, 
        
            802
                            repository=repository, 
        
            803
                            file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 
        
            804
                            branches=all_refs, 
        
            805
                            current=branch, 
        
            806
                            mode=mode, 
        
            807
                            mimetype=mimetype, 
        
            808
                            detailedtype=magic.from_file(path), 
        
            809
                            size=size, 
        
            810
                            icon=icon, 
        
            811
                            subpath=os.path.join("/", subpath), 
        
            812
                            extension=pathlib.Path(path).suffix, 
        
            813
                            basename=os.path.basename(path), 
        
            814
                            contents=contents, 
        
            815
                            remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            816
                            is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            817
                            repo_data=repo_data, 
        
            818
                    ) 
        
            819
            @repositories.route("/<username>/<repository>/commit/<sha>") 
        
            822
            def repository_commit(username, repository, sha): 
        
            823
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            824
                if not os.path.exists(server_repo_location): 
        
            825
                    flask.abort(404) 
        
            826
                if not (get_visibility(username, repository) or get_permission_level( 
        
            827
                        flask.session.get("username"), username, 
        
            828
                        repository) is not None): 
        
            829
                    flask.abort(403) 
        
            830
                if not os.path.exists(server_repo_location): 
        
            832
                    return flask.render_template("errors/not-found.html"), 404 
        
            833
                repo = git.Repo(server_repo_location) 
        
            835
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            836
                files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r", 
        
            838
                                    "--name-only", "--no-commit-id", sha).decode().split("\n")[:-1] 
        
            839
                return flask.render_template( 
        
            841
                        "repo-commit.html", 
        
            842
                        username=username, 
        
            843
                        repository=repository, 
        
            844
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            845
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            846
                        diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff", 
        
            847
                                                str(sha) + "^!", "--", file).decode().split("\n") for 
        
            848
                              file in files}, 
        
            849
                        data=db.session.get(Commit, f"/{username}/{repository}/{sha}"), 
        
            850
                        repo_data=repo_data, 
        
            851
                        comment_query=Comment.query, 
        
            852
                        permission_level=get_permission_level(flask.session.get("username"), username, repository), 
        
            853
                ) 
        
            854
            @repositories.route("/<username>/<repository>/commit/<sha>/add_comment", methods=["POST"]) 
        
            857
            def repository_commit_add_comment(username, repository, sha): 
        
            858
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            859
                if not os.path.exists(server_repo_location): 
        
            860
                    flask.abort(404) 
        
            861
                if not (get_visibility(username, repository) or get_permission_level( 
        
            862
                        flask.session.get("username"), username, 
        
            863
                        repository) is not None): 
        
            864
                    flask.abort(403) 
        
            865
                comment = Comment( 
        
            867
                    db.session.get(User, flask.session.get("username")), 
        
            868
                    db.session.get(Repo, f"/{username}/{repository}"), 
        
            869
                    db.session.get(Commit, f"/{username}/{repository}/{sha}"), 
        
            870
                    flask.request.form["comment"], 
        
            871
                    flask.request.form["file"], 
        
            872
                    flask.request.form["line"], 
        
            873
                ) 
        
            874
                db.session.add(comment) 
        
            876
                db.session.commit() 
        
            877
                return flask.redirect( 
        
            879
                    flask.url_for(".repository_commit", username=username, repository=repository, sha=sha), 
        
            880
                    code=303 
        
            881
                ) 
        
            882
            @repositories.route("/<username>/<repository>/commit/<sha>/delete_comment/<int:id>", methods=["POST"]) 
        
            885
            def repository_commit_delete_comment(username, repository, sha, id): 
        
            886
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            887
                comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first() 
        
            888
                commit = Commit.query.filter_by(identifier=f"/{username}/{repository}/{sha}").first() 
        
            889
                if ( 
        
            890
                    comment.owner.username == flask.session.get("username") 
        
            891
                    or get_permission_level(flask.session.get("username"), username, repository) >= 2 
        
            892
                    or comment.commit.owner.username == flask.session.get("username") 
        
            893
                ): 
        
            894
                    db.session.delete(comment) 
        
            895
                    db.session.commit() 
        
            896
                return flask.redirect( 
        
            898
                    flask.url_for(".repository_commit", username=username, repository=repository, sha=sha), 
        
            899
                    code=303 
        
            900
                ) 
        
            901
            @repositories.route("/<username>/<repository>/commit/<sha>/resolve_comment/<int:id>", methods=["POST"]) 
        
            904
            def repository_commit_resolve_comment(username, repository, sha, id): 
        
            905
                comment = Comment.query.filter_by(identifier=f"/{username}/{repository}/{id}").first() 
        
            906
                if ( 
        
            907
                    comment.commit.owner.username == flask.session.get("username") 
        
            908
                    or get_permission_level(flask.session.get("username"), username, repository) >= 2 
        
            909
                    or comment.owner.username == flask.session.get("username") 
        
            910
                ): 
        
            911
                    comment.state = int(not comment.state) 
        
            912
                    db.session.commit() 
        
            913
                return flask.redirect( 
        
            915
                    flask.url_for(".repository_commit", username=username, repository=repository, sha=sha), 
        
            916
                    code=303 
        
            917
                ) 
        
            918
            @repositories.route("/<username>/<repository>/forum/") 
        
            921
            def repository_forum(username, repository): 
        
            922
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            923
                if not os.path.exists(server_repo_location): 
        
            924
                    flask.abort(404) 
        
            925
                if not (get_visibility(username, repository) or get_permission_level( 
        
            926
                        flask.session.get("username"), username, 
        
            927
                        repository) is not None): 
        
            928
                    flask.abort(403) 
        
            929
                repo = git.Repo(server_repo_location) 
        
            931
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            932
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            933
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            934
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            935
                return flask.render_template( 
        
            937
                        "repo-forum.html", 
        
            938
                        username=username, 
        
            939
                        repository=repository, 
        
            940
                        repo_data=repo_data, 
        
            941
                        relationships=relationships, 
        
            942
                        repo=repo, 
        
            943
                        user_relationship=user_relationship, 
        
            944
                        Post=Post, 
        
            945
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            946
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            947
                        default_branch=repo_data.default_branch 
        
            948
                ) 
        
            949
            @repositories.route("/<username>/<repository>/forum/search") 
        
            952
            def repository_forum_search(username, repository): 
        
            953
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            954
                if not os.path.exists(server_repo_location): 
        
            955
                    flask.abort(404) 
        
            956
                if not (get_visibility(username, repository) or get_permission_level( 
        
            957
                            flask.session.get("username"), username, 
        
            958
                            repository) is not None): 
        
            959
                            flask.abort(403) 
        
            960
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            962
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            963
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            964
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            965
                query = flask.request.args.get("q") 
        
            967
                page_number = flask.request.args.get("page", 1, type=int) 
        
            969
                if flask.session.get("username"): 
        
            970
                    default_page_length = db.session.get(User, flask.session.get("username")).default_page_length 
        
            971
                else: 
        
            972
                    default_page_length = 16 
        
            973
                page_length = flask.request.args.get("per_page", default_page_length, type=int) 
        
            975
                results = Post.query.filter(Post.repo == repo_data).filter(Post.subject.ilike(f"%{query}%") | Post.message.ilike(f"%{query}%")).order_by(Post.last_updated.desc()).paginate(page=page_number, per_page=page_length) 
        
            977
                if results.has_next: 
        
            979
                    next_page = results.next_num 
        
            980
                else: 
        
            981
                    next_page = None 
        
            982
                if results.has_prev: 
        
            984
                    prev_page = results.prev_num 
        
            985
                else: 
        
            986
                    prev_page = None 
        
            987
                return flask.render_template( 
        
            989
                    "repo-forum-search.html", 
        
            990
                    username=username, 
        
            991
                    repository=repository, 
        
            992
                    repo_data=repo_data, 
        
            993
                    relationships=relationships, 
        
            994
                    user_relationship=user_relationship, 
        
            995
                    query=query, 
        
            996
                    results=results, 
        
            997
                    Post=Post, 
        
            998
                    remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            999
                    is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1000
                    default_branch=repo_data.default_branch, 
        
            1001
                    page_number=page_number, 
        
            1002
                    page_length=page_length, 
        
            1003
                    next_page=next_page, 
        
            1004
                    prev_page=prev_page, 
        
            1005
                    num_pages=results.pages 
        
            1006
                ) 
        
            1007
            @repositories.route("/<username>/<repository>/forum/topic/<int:id>") 
        
            1010
            def repository_forum_topic(username, repository, id): 
        
            1011
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1012
                if not os.path.exists(server_repo_location): 
        
            1013
                    flask.abort(404) 
        
            1014
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1015
                        flask.session.get("username"), username, 
        
            1016
                        repository) is not None): 
        
            1017
                    flask.abort(403) 
        
            1018
                if not os.path.exists(server_repo_location): 
        
            1020
                    return flask.render_template("errors/not-found.html"), 404 
        
            1021
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1023
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1024
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1025
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1026
                post = Post.query.filter_by(id=id).first() 
        
            1028
                return flask.render_template( 
        
            1030
                        "repo-topic.html", 
        
            1031
                        username=username, 
        
            1032
                        repository=repository, 
        
            1033
                        repo_data=repo_data, 
        
            1034
                        relationships=relationships, 
        
            1035
                        user_relationship=user_relationship, 
        
            1036
                        post=post, 
        
            1037
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1038
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1039
                        default_branch=repo_data.default_branch 
        
            1040
                ) 
        
            1041
            @repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"]) 
        
            1044
            def repository_forum_new(username, repository): 
        
            1045
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1046
                if not os.path.exists(server_repo_location): 
        
            1047
                    flask.abort(404) 
        
            1048
                if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level( 
        
            1049
                        flask.session.get("username"), username, 
        
            1050
                        repository) is not None): 
        
            1051
                    flask.abort(403) 
        
            1052
                if not os.path.exists(server_repo_location): 
        
            1054
                    return flask.render_template("errors/not-found.html"), 404 
        
            1055
                repo = git.Repo(server_repo_location) 
        
            1057
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1058
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1059
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1060
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1061
                post = Post(user, repo_data, None, flask.request.form["subject"], 
        
            1063
                            flask.request.form["message"]) 
        
            1064
                db.session.add(post) 
        
            1066
                db.session.commit() 
        
            1067
                return flask.redirect( 
        
            1069
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1070
                                      post_id=post.number), 
        
            1071
                        code=303) 
        
            1072
            @repositories.route("/<username>/<repository>/forum/<int:post_id>") 
        
            1075
            def repository_forum_thread(username, repository, post_id): 
        
            1076
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1077
                if not os.path.exists(server_repo_location): 
        
            1078
                    flask.abort(404) 
        
            1079
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1080
                        flask.session.get("username"), username, 
        
            1081
                        repository) is not None): 
        
            1082
                    flask.abort(403) 
        
            1083
                if not os.path.exists(server_repo_location): 
        
            1085
                    return flask.render_template("errors/not-found.html"), 404 
        
            1086
                repo = git.Repo(server_repo_location) 
        
            1088
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1089
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1090
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1091
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1092
                if user: 
        
            1094
                    max_post_nesting = user.max_post_nesting 
        
            1095
                else: 
        
            1096
                    max_post_nesting = 2 
        
            1097
                return flask.render_template( 
        
            1099
                        "repo-forum-thread.html", 
        
            1100
                        username=username, 
        
            1101
                        repository=repository, 
        
            1102
                        repo_data=repo_data, 
        
            1103
                        relationships=relationships, 
        
            1104
                        repo=repo, 
        
            1105
                        Post=Post, 
        
            1106
                        user_relationship=user_relationship, 
        
            1107
                        post_id=post_id, 
        
            1108
                        max_post_nesting=max_post_nesting, 
        
            1109
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1110
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1111
                        parent=Post.query.filter_by(repo=repo_data, number=post_id).first(), 
        
            1112
                        has_permission=not ((not get_permission_level(flask.session.get("username"), username, 
        
            1113
                                                                 repository)) and db.session.get(Post, 
        
            1114
                                                                                                 f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")), 
        
            1115
                ) 
        
            1116
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state", 
        
            1119
                                methods=["POST"]) 
        
            1120
            def repository_forum_change_state(username, repository, post_id): 
        
            1121
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1122
                if not os.path.exists(server_repo_location): 
        
            1123
                    flask.abort(404) 
        
            1124
                if (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"): 
        
            1125
                    flask.abort(403) 
        
            1126
                repo = git.Repo(server_repo_location) 
        
            1128
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1129
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1130
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1131
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1132
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 
        
            1134
                if not post: 
        
            1136
                    flask.abort(404) 
        
            1137
                if post.parent: 
        
            1138
                    flask.abort(400) 
        
            1139
                post.state = int(flask.request.form["new-state"]) 
        
            1141
                db.session.commit() 
        
            1143
                return flask.redirect( 
        
            1145
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1146
                                      post_id=post_id), 
        
            1147
                        code=303) 
        
            1148
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"]) 
        
            1151
            def repository_forum_reply(username, repository, post_id): 
        
            1152
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1153
                if not os.path.exists(server_repo_location): 
        
            1154
                    flask.abort(404) 
        
            1155
                if not ((flask.session.get("username") and get_visibility(username, repository)) or get_permission_level( 
        
            1156
                        flask.session.get("username"), username, 
        
            1157
                        repository) is not None): 
        
            1158
                    flask.abort(403) 
        
            1159
                if not os.path.exists(server_repo_location): 
        
            1161
                    return flask.render_template("errors/not-found.html"), 404 
        
            1162
                repo = git.Repo(server_repo_location) 
        
            1164
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1165
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1166
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1167
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1168
                if not user: 
        
            1169
                    flask.abort(401) 
        
            1170
                parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 
        
            1172
                post = Post(user, repo_data, parent, flask.request.form["subject"], 
        
            1173
                            flask.request.form["message"]) 
        
            1174
                db.session.add(post) 
        
            1176
                post.update_date() 
        
            1177
                db.session.commit() 
        
            1178
                return flask.redirect( 
        
            1180
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1181
                                      post_id=post_id), 
        
            1182
                        code=303) 
        
            1183
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["POST"]) 
        
            1186
            def repository_forum_edit(username, repository, post_id): 
        
            1187
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1188
                if not os.path.exists(server_repo_location): 
        
            1189
                    flask.abort(404) 
        
            1190
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1191
                    flask.session.get("username"), username, 
        
            1192
                    repository) is not None): 
        
            1193
                    flask.abort(403) 
        
            1194
                if not os.path.exists(server_repo_location): 
        
            1196
                    return flask.render_template("errors/not-found.html"), 404 
        
            1197
                repo = git.Repo(server_repo_location) 
        
            1199
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1200
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1201
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1202
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1203
                if not user: 
        
            1204
                    flask.abort(401) 
        
            1205
                post = db.session.get(Post, f"/{username}/{repository}/{post_id}") 
        
            1206
                if user != post.owner: 
        
            1207
                    flask.abort(403) 
        
            1208
                post.subject = flask.request.form["subject"] 
        
            1210
                post.message = flask.request.form["message"] 
        
            1211
                post.html = markdown.markdown2html(post.message).prettify() 
        
            1212
                post.update_date() 
        
            1213
                db.session.commit() 
        
            1214
                return flask.redirect( 
        
            1216
                    flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1217
                                    post_id=post_id), 
        
            1218
                    code=303) 
        
            1219
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/edit", methods=["GET"]) 
        
            1222
            def repository_forum_edit_form(username, repository, post_id): 
        
            1223
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1224
                if not os.path.exists(server_repo_location): 
        
            1225
                    flask.abort(404) 
        
            1226
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1227
                    flask.session.get("username"), username, 
        
            1228
                    repository) is not None): 
        
            1229
                    flask.abort(403) 
        
            1230
                if not os.path.exists(server_repo_location): 
        
            1232
                    return flask.render_template("errors/not-found.html"), 404 
        
            1233
                repo = git.Repo(server_repo_location) 
        
            1235
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1236
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1237
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1238
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1239
                if not user: 
        
            1240
                    flask.abort(401) 
        
            1241
                post = db.session.get(Post, f"/{username}/{repository}/{post_id}") 
        
            1242
                if user != post.owner: 
        
            1243
                    flask.abort(403) 
        
            1244
                return flask.render_template( 
        
            1246
                        "repo-forum-edit.html", 
        
            1247
                        username=username, 
        
            1248
                        repository=repository, 
        
            1249
                        repo_data=repo_data, 
        
            1250
                        relationships=relationships, 
        
            1251
                        repo=repo, 
        
            1252
                        user_relationship=user_relationship, 
        
            1253
                        post=post, 
        
            1254
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1255
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1256
                        default_branch=repo_data.default_branch 
        
            1257
                ) 
        
            1258
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", 
        
            1260
                                defaults={"score": 1}) 
        
            1261
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown", 
        
            1262
                                defaults={"score": -1}) 
        
            1263
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0}) 
        
            1264
            def repository_forum_vote(username, repository, post_id, score): 
        
            1265
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1266
                if not os.path.exists(server_repo_location): 
        
            1267
                    flask.abort(404) 
        
            1268
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1269
                        flask.session.get("username"), username, 
        
            1270
                        repository) is not None): 
        
            1271
                    flask.abort(403) 
        
            1272
                if not os.path.exists(server_repo_location): 
        
            1274
                    return flask.render_template("errors/not-found.html"), 404 
        
            1275
                repo = git.Repo(server_repo_location) 
        
            1277
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1278
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1279
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1280
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1281
                if not user: 
        
            1282
                    flask.abort(401) 
        
            1283
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 
        
            1285
                if score: 
        
            1287
                    old_relationship = PostVote.query.filter_by(user_username=user.username, 
        
            1288
                                                                post_identifier=post.identifier).first() 
        
            1289
                    if old_relationship: 
        
            1290
                        if score == old_relationship.vote_score: 
        
            1291
                            db.session.delete(old_relationship) 
        
            1292
                            post.vote_sum -= old_relationship.vote_score 
        
            1293
                        else: 
        
            1294
                            post.vote_sum -= old_relationship.vote_score 
        
            1295
                            post.vote_sum += score 
        
            1296
                            old_relationship.vote_score = score 
        
            1297
                    else: 
        
            1298
                        relationship = PostVote(user, post, score) 
        
            1299
                        post.vote_sum += score 
        
            1300
                        db.session.add(relationship) 
        
            1301
                    db.session.commit() 
        
            1303
                user_vote = PostVote.query.filter_by(user_username=user.username, 
        
            1305
                                                     post_identifier=post.identifier).first() 
        
            1306
                response = flask.make_response( 
        
            1307
                        str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0)) 
        
            1308
                response.content_type = "text/plain" 
        
            1309
                return response 
        
            1311
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/label", methods=["POST"]) 
        
            1314
            def repository_forum_label(username, repository, post_id): 
        
            1315
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1316
                if not os.path.exists(server_repo_location): 
        
            1317
                    flask.abort(404) 
        
            1318
                if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1319
                    flask.abort(403) 
        
            1320
                repo = git.Repo(server_repo_location) 
        
            1322
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1323
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1324
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1325
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1326
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 
        
            1328
                if not post: 
        
            1330
                    flask.abort(404) 
        
            1331
                if post.parent: 
        
            1332
                    flask.abort(400) 
        
            1333
                label = db.session.get(Label, flask.request.form["label"]) 
        
            1335
                if PostLabel.query.filter_by(post=post, label=label).first(): 
        
            1337
                    return flask.redirect( 
        
            1338
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1339
                                      post_id=post_id), 
        
            1340
                        code=303) 
        
            1341
                post_label = PostLabel(post, label) 
        
            1343
                db.session.add(post_label) 
        
            1344
                db.session.commit() 
        
            1346
                return flask.redirect( 
        
            1348
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1349
                                          post_id=post_id), 
        
            1350
                        code=303) 
        
            1351
            @repositories.route("/<username>/<repository>/forum/<int:post_id>/remove-label") 
        
            1354
            def repository_forum_remove_label(username, repository, post_id): 
        
            1355
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1356
                if not os.path.exists(server_repo_location): 
        
            1357
                    flask.abort(404) 
        
            1358
                if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1359
                    flask.abort(403) 
        
            1360
                repo = git.Repo(server_repo_location) 
        
            1362
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1363
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1364
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1365
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1366
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first() 
        
            1368
                if not post: 
        
            1370
                    flask.abort(404) 
        
            1371
                if post.parent: 
        
            1372
                    flask.abort(400) 
        
            1373
                label = db.session.get(Label, flask.request.args["label"]) 
        
            1375
                post_label = PostLabel.query.filter_by(post=post, label=label).first() 
        
            1377
                db.session.delete(post_label) 
        
            1378
                db.session.commit() 
        
            1380
                return flask.redirect( 
        
            1382
                        flask.url_for(".repository_forum_thread", username=username, repository=repository, 
        
            1383
                                          post_id=post_id), 
        
            1384
                        code=303) 
        
            1385
            @repositories.route("/<username>/<repository>/favourite") 
        
            1388
            def repository_favourite(username, repository): 
        
            1389
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1390
                if not os.path.exists(server_repo_location): 
        
            1391
                    flask.abort(404) 
        
            1392
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1393
                        flask.session.get("username"), username, 
        
            1394
                        repository) is not None): 
        
            1395
                    flask.abort(403) 
        
            1396
                if not os.path.exists(server_repo_location): 
        
            1398
                    return flask.render_template("errors/not-found.html"), 404 
        
            1399
                repo = git.Repo(server_repo_location) 
        
            1401
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1402
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1403
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1404
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1405
                if not user: 
        
            1406
                    flask.abort(401) 
        
            1407
                old_relationship = RepoFavourite.query.filter_by(user_username=user.username, 
        
            1409
                                                                 repo_route=repo_data.route).first() 
        
            1410
                if old_relationship: 
        
            1411
                    db.session.delete(old_relationship) 
        
            1412
                else: 
        
            1413
                    relationship = RepoFavourite(user, repo_data) 
        
            1414
                    db.session.add(relationship) 
        
            1415
                db.session.commit() 
        
            1417
                return flask.redirect(flask.url_for("favourites"), code=303) 
        
            1419
            @repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 
        
            1422
            def repository_users(username, repository): 
        
            1423
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1424
                if not os.path.exists(server_repo_location): 
        
            1425
                    flask.abort(404) 
        
            1426
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1427
                        flask.session.get("username"), username, 
        
            1428
                        repository) is not None): 
        
            1429
                    flask.abort(403) 
        
            1430
                if not os.path.exists(server_repo_location): 
        
            1432
                    return flask.render_template("errors/not-found.html"), 404 
        
            1433
                repo = git.Repo(server_repo_location) 
        
            1435
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1436
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1437
                relationships = RepoAccess.query.filter_by(repo=repo_data) 
        
            1438
                user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first() 
        
            1439
                if flask.request.method == "GET": 
        
            1441
                    return flask.render_template( 
        
            1442
                            "repo-users.html", 
        
            1443
                            username=username, 
        
            1444
                            repository=repository, 
        
            1445
                            repo_data=repo_data, 
        
            1446
                            relationships=relationships, 
        
            1447
                            repo=repo, 
        
            1448
                            user_relationship=user_relationship, 
        
            1449
                            remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1450
                            is_favourite=get_favourite(flask.session.get("username"), username, repository) 
        
            1451
                    ) 
        
            1452
                else: 
        
            1453
                    if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1454
                        flask.abort(401) 
        
            1455
                    if flask.request.form.get("new-username"): 
        
            1457
                        # Create new relationship 
        
            1458
                        new_user = User.query.filter_by( 
        
            1459
                                username=flask.request.form.get("new-username")).first() 
        
            1460
                        relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level")) 
        
            1461
                        db.session.add(relationship) 
        
            1462
                        db.session.commit() 
        
            1463
                    if flask.request.form.get("update-username"): 
        
            1464
                        # Create new relationship 
        
            1465
                        updated_user = User.query.filter_by( 
        
            1466
                                username=flask.request.form.get("update-username")).first() 
        
            1467
                        relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first() 
        
            1468
                        if flask.request.form.get("update-level") == -1: 
        
            1469
                            relationship.delete() 
        
            1470
                        else: 
        
            1471
                            relationship.access_level = flask.request.form.get("update-level") 
        
            1472
                        db.session.commit() 
        
            1473
                    return flask.redirect( 
        
            1475
                            app.url_for(".repository_users", username=username, repository=repository)) 
        
            1476
            @repositories.route("/<username>/<repository>/branches/") 
        
            1479
            def repository_branches(username, repository): 
        
            1480
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1481
                if not os.path.exists(server_repo_location): 
        
            1482
                    flask.abort(404) 
        
            1483
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1484
                        flask.session.get("username"), username, 
        
            1485
                        repository) is not None): 
        
            1486
                    flask.abort(403) 
        
            1487
                if not os.path.exists(server_repo_location): 
        
            1489
                    return flask.render_template("errors/not-found.html"), 404 
        
            1490
                repo = git.Repo(server_repo_location) 
        
            1492
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1493
                return flask.render_template( 
        
            1495
                        "repo-branches.html", 
        
            1496
                        username=username, 
        
            1497
                        repository=repository, 
        
            1498
                        repo_data=repo_data, 
        
            1499
                        repo=repo, 
        
            1500
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1501
                        is_favourite=get_favourite(flask.session.get("username"), username, repository) 
        
            1502
                ) 
        
            1503
            @repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 
        
            1506
            @repositories.route("/<username>/<repository>/log/<branch>/") 
        
            1507
            def repository_log(username, repository, branch): 
        
            1508
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1509
                if not os.path.exists(server_repo_location): 
        
            1510
                    flask.abort(404) 
        
            1511
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1512
                        flask.session.get("username"), username, 
        
            1513
                        repository) is not None): 
        
            1514
                    flask.abort(403) 
        
            1515
                if not os.path.exists(server_repo_location): 
        
            1517
                    return flask.render_template("errors/not-found.html"), 404 
        
            1518
                repo = git.Repo(server_repo_location) 
        
            1520
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1521
                if not repo_data.default_branch: 
        
            1522
                    if repo.heads: 
        
            1523
                        repo_data.default_branch = repo.heads[0].name 
        
            1524
                    else: 
        
            1525
                        return flask.render_template("empty.html", 
        
            1526
                                                     remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            1527
                if not branch: 
        
            1528
                    branch = repo_data.default_branch 
        
            1529
                    return flask.redirect(f"./{branch}", code=302) 
        
            1530
                if branch.startswith("tag:"): 
        
            1532
                    ref = f"tags/{branch[4:]}" 
        
            1533
                elif branch.startswith("~"): 
        
            1534
                    ref = branch[1:] 
        
            1535
                else: 
        
            1536
                    ref = f"heads/{branch}" 
        
            1537
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            1539
                try: 
        
            1541
                    repo.git.checkout("-f", ref) 
        
            1542
                except git.exc.GitCommandError: 
        
            1543
                    return flask.render_template("errors/not-found.html"), 404 
        
            1544
                branches = repo.heads 
        
            1546
                all_refs = [] 
        
            1548
                for ref in repo.heads: 
        
            1549
                    all_refs.append((ref, "head")) 
        
            1550
                for ref in repo.tags: 
        
            1551
                    all_refs.append((ref, "tag")) 
        
            1552
                commit_list = [f"/{username}/{repository}/{sha}" for sha in 
        
            1554
                               git_command(server_repo_location, None, "log", 
        
            1555
                                           "--format='%H'").decode().split("\n")] 
        
            1556
                commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc()) 
        
            1558
                page_number = flask.request.args.get("page", 1, type=int) 
        
            1559
                if flask.session.get("username"): 
        
            1560
                    default_page_length = db.session.get(User, flask.session.get("username")).default_page_length 
        
            1561
                else: 
        
            1562
                    default_page_length = 16 
        
            1563
                page_length = flask.request.args.get("per_page", default_page_length, type=int) 
        
            1564
                page_listing = db.paginate(commits, page=page_number, per_page=page_length) 
        
            1565
                if page_listing.has_next: 
        
            1567
                    next_page = page_listing.next_num 
        
            1568
                else: 
        
            1569
                    next_page = None 
        
            1570
                if page_listing.has_prev: 
        
            1572
                    prev_page = page_listing.prev_num 
        
            1573
                else: 
        
            1574
                    prev_page = None 
        
            1575
                return flask.render_template( 
        
            1577
                        "repo-log.html", 
        
            1578
                        username=username, 
        
            1579
                        repository=repository, 
        
            1580
                        branches=all_refs, 
        
            1581
                        current=branch, 
        
            1582
                        repo_data=repo_data, 
        
            1583
                        repo=repo, 
        
            1584
                        commits=page_listing, 
        
            1585
                        remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1586
                        is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1587
                        page_number=page_number, 
        
            1588
                        page_length=page_length, 
        
            1589
                        next_page=next_page, 
        
            1590
                        prev_page=prev_page, 
        
            1591
                        num_pages=page_listing.pages 
        
            1592
                ) 
        
            1593
            @repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"]) 
        
            1596
            def repository_prs(username, repository): 
        
            1597
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1598
                if not os.path.exists(server_repo_location): 
        
            1599
                    flask.abort(404) 
        
            1600
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1601
                        flask.session.get("username"), username, 
        
            1602
                        repository) is not None): 
        
            1603
                    flask.abort(403) 
        
            1604
                if not os.path.exists(server_repo_location): 
        
            1606
                    return flask.render_template("errors/not-found.html"), 404 
        
            1607
                if flask.request.method == "GET": 
        
            1609
                    repo = git.Repo(server_repo_location) 
        
            1610
                    repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1611
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            1612
                    return flask.render_template( 
        
            1614
                            "repo-prs.html", 
        
            1615
                            username=username, 
        
            1616
                            repository=repository, 
        
            1617
                            repo_data=repo_data, 
        
            1618
                            repo=repo, 
        
            1619
                            PullRequest=PullRequest, 
        
            1620
                            remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1621
                            is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1622
                            default_branch=repo_data.default_branch, 
        
            1623
                            branches=repo.branches 
        
            1624
                    ) 
        
            1625
                elif "id" not in flask.request.form: 
        
            1627
                    repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1628
                    head = flask.request.form.get("head") 
        
            1629
                    head_route = flask.request.form.get("headroute") 
        
            1630
                    base = flask.request.form.get("base") 
        
            1631
                    if not head and base and head_route: 
        
            1633
                        return flask.redirect(".", 400) 
        
            1634
                    head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/"))) 
        
            1636
                    base_repo = git.Repo(server_repo_location) 
        
            1637
                    if head not in head_repo.branches or base not in base_repo.branches: 
        
            1639
                        flask.flash(Markup( 
        
            1640
                                "<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")), 
        
            1641
                                category="error") 
        
            1642
                        return flask.redirect(".", 303) 
        
            1643
                    head_data = db.session.get(Repo, head_route) 
        
            1645
                    if not head_data.visibility: 
        
            1646
                        flask.flash(Markup( 
        
            1647
                                "<iconify-icon icon='mdi:error'></iconify-icon>" + _( 
        
            1648
                                        "Head can't be restricted")), 
        
            1649
                                category="error") 
        
            1650
                        return flask.redirect(".", 303) 
        
            1651
                    pull_request = PullRequest(head_data, head, repo_data, base, 
        
            1653
                                               db.session.get(User, flask.session["username"])) 
        
            1654
                    db.session.add(pull_request) 
        
            1656
                    db.session.commit() 
        
            1657
                    # Create the notification 
        
            1659
                    notification = Notification({"type": "pr", "head": pull_request.head.route, "base": pull_request.base.route, "pr": pull_request.id}) 
        
            1660
                    db.session.add(notification) 
        
            1661
                    db.session.commit() 
        
            1662
                    # Send a notification to all users who have enabled PR notifications for this repo 
        
            1664
                    for relationship in RepoFavourite.query.filter_by(repo_route=pull_request.base.route, notify_pr=True).all(): 
        
            1665
                        user = relationship.user 
        
            1666
                        user_notification = UserNotification(user, notification, 1) 
        
            1667
                        db.session.add(user_notification) 
        
            1668
                        db.session.commit() 
        
            1669
                        celery_tasks.send_notification.apply_async(args=[user_notification.id]) 
        
            1670
                    return flask.redirect(".", 303) 
        
            1672
                else: 
        
            1673
                    id = flask.request.form.get("id") 
        
            1674
                    pull_request = db.session.get(PullRequest, id) 
        
            1675
                    if not pull_request: 
        
            1677
                        flask.abort(404) 
        
            1678
                    if not (get_visibility(username, repository) or get_permission_level( 
        
            1680
                            flask.session.get("username"), username, 
        
            1681
                            repository) >= 1 or pull_request.owner.username == flask.session.get("username")): 
        
            1682
                        flask.abort(403) 
        
            1683
                    if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1685
                        flask.abort(401) 
        
            1686
                    repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1688
                    if pull_request: 
        
            1690
                        pull_request.resolves_list = flask.request.form.get("resolves") 
        
            1691
                        db.session.commit() 
        
            1692
                    return flask.redirect(".", 303) 
        
            1694
            @repositories.route("/<username>/<repository>/prs/merge", methods=["POST"]) 
        
            1697
            def repository_prs_merge(username, repository): 
        
            1698
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1699
                if not os.path.exists(server_repo_location): 
        
            1700
                    flask.abort(404) 
        
            1701
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1702
                        flask.session.get("username"), username, 
        
            1703
                        repository) is not None): 
        
            1704
                    flask.abort(403) 
        
            1705
                if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1707
                    flask.abort(401) 
        
            1708
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1710
                repo = git.Repo(server_repo_location) 
        
            1711
                id = flask.request.form.get("id") 
        
            1712
                pull_request = db.session.get(PullRequest, id) 
        
            1714
                if pull_request: 
        
            1716
                    result = celery_tasks.merge_heads.delay( 
        
            1717
                            pull_request.head_route, 
        
            1718
                            pull_request.head_branch, 
        
            1719
                            pull_request.base_route, 
        
            1720
                            pull_request.base_branch, 
        
            1721
                            pull_request.id, 
        
            1722
                            simulate=True 
        
            1723
                    ) 
        
            1724
                    task_result = worker.AsyncResult(result.id) 
        
            1725
                    return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)       # should be 202 Accepted but we must use a redirect 
        
            1727
                    # db.session.delete(pull_request) 
        
            1728
                    # db.session.commit() 
        
            1729
                else: 
        
            1730
                    flask.abort(400) 
        
            1731
            @repositories.route("/<username>/<repository>/prs/<int:id>/merge") 
        
            1734
            def repository_prs_merge_stage_two(username, repository, id): 
        
            1735
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1736
                if not os.path.exists(server_repo_location): 
        
            1737
                    flask.abort(404) 
        
            1738
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1739
                        flask.session.get("username"), username, 
        
            1740
                        repository) is not None): 
        
            1741
                    flask.abort(403) 
        
            1742
                if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1744
                    flask.abort(401) 
        
            1745
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1747
                repo = git.Repo(server_repo_location) 
        
            1748
                pull_request = db.session.get(PullRequest, id) 
        
            1750
                if pull_request: 
        
            1752
                    result = celery_tasks.merge_heads.delay( 
        
            1753
                            pull_request.head_route, 
        
            1754
                            pull_request.head_branch, 
        
            1755
                            pull_request.base_route, 
        
            1756
                            pull_request.base_branch, 
        
            1757
                            pull_request.id, 
        
            1758
                            simulate=False 
        
            1759
                    ) 
        
            1760
                    task_result = worker.AsyncResult(result.id) 
        
            1761
                    db.session.commit() 
        
            1763
                    return flask.redirect(f"/task/{result.id}?pr-id={id}", 303) 
        
            1765
                    # db.session.delete(pull_request) 
        
            1766
                else: 
        
            1767
                    flask.abort(400) 
        
            1768
            @app.route("/task/<task_id>") 
        
            1771
            def task_monitor(task_id): 
        
            1772
                task_result = worker.AsyncResult(task_id) 
        
            1773
                if flask.request.args.get("partial"): 
        
            1775
                    # htmx partial update 
        
            1776
                    return render_block("task-monitor.html", "content", result=task_result, query_string=flask.request.query_string.decode(), delay=1000) 
        
            1777
                # Since most tasks finish rather quickly, the initial delay is faster, so it doesn't wait for too long 
        
            1779
                return flask.render_template("task-monitor.html", result=task_result, query_string=flask.request.query_string.decode(), delay=125) 
        
            1780
            @repositories.route("/<username>/<repository>/prs/delete", methods=["POST"]) 
        
            1783
            def repository_prs_delete(username, repository): 
        
            1784
                server_repo_location = os.path.join(config.REPOS_PATH, username, repository) 
        
            1785
                if not os.path.exists(server_repo_location): 
        
            1786
                    flask.abort(404) 
        
            1787
                if not (get_visibility(username, repository) or get_permission_level( 
        
            1788
                        flask.session.get("username"), username, 
        
            1789
                        repository) is not None): 
        
            1790
                    flask.abort(403) 
        
            1791
                if not get_permission_level(flask.session.get("username"), username, repository): 
        
            1793
                    flask.abort(401) 
        
            1794
                repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            1796
                repo = git.Repo(server_repo_location) 
        
            1797
                id = flask.request.form.get("id") 
        
            1798
                pull_request = db.session.get(PullRequest, id) 
        
            1800
                if pull_request: 
        
            1802
                    pull_request.state = 2 
        
            1803
                    db.session.commit() 
        
            1804
                return flask.redirect(".", 303) 
        
            1806
            @repositories.route("/<username>/<repository>/settings/") 
        
            1809
            def repository_settings(username, repository): 
        
            1810
                if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1811
                    flask.abort(401) 
        
            1812
                repo = git.Repo(os.path.join(config.REPOS_PATH, username, repository)) 
        
            1814
                site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/{repository}</code>") 
        
            1816
                primary_site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/</code>") 
        
            1817
                return flask.render_template("repo-settings.html", username=username, repository=repository, 
        
            1819
                                             repo_data=db.session.get(Repo, f"/{username}/{repository}"), 
        
            1820
                                             branches=[branch.name for branch in repo.branches], 
        
            1821
                                             site_link=site_link, primary_site_link=primary_site_link, 
        
            1822
                                             remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            1823
                                             is_favourite=get_favourite(flask.session.get("username"), username, repository), 
        
            1824
                ) 
        
            1825
            @repositories.route("/<username>/<repository>/settings/", methods=["POST"]) 
        
            1828
            def repository_settings_post(username, repository): 
        
            1829
                if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1830
                    flask.abort(401) 
        
            1831
                repo = db.session.get(Repo, f"/{username}/{repository}") 
        
            1833
                repo.visibility = flask.request.form.get("visibility", type=int) 
        
            1835
                repo.info = flask.request.form.get("description") 
        
            1836
                repo.default_branch = flask.request.form.get("default_branch") 
        
            1837
                repo.url = flask.request.form.get("url") 
        
            1838
                # Update site settings 
        
            1840
                had_site = repo.has_site 
        
            1841
                old_branch = repo.site_branch 
        
            1842
                if flask.request.form.get("site_branch"): 
        
            1843
                    repo.site_branch = flask.request.form.get("site_branch") 
        
            1844
                    if flask.request.form.get("primary_site"): 
        
            1845
                        if had_site != 2: 
        
            1846
                            # Remove primary site from other repos 
        
            1847
                            for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=2): 
        
            1848
                                other_repo.has_site = 1                           # switch it to a regular site 
        
            1849
                                flask.flash(Markup( 
        
            1850
                                        _("Your repository {repository} has been demoted from a primary site to a regular site because there can only be one primary site per user.").format( 
        
            1851
                                                repository=other_repo.route 
        
            1852
                                        )), category="warning") 
        
            1853
                        repo.has_site = 2 
        
            1854
                    else: 
        
            1855
                        repo.has_site = 1 
        
            1856
                else: 
        
            1857
                    repo.site_branch = None 
        
            1858
                    repo.has_site = 0 
        
            1859
                db.session.commit() 
        
            1861
                if not (had_site, old_branch) == (repo.has_site, repo.site_branch): 
        
            1863
                    # Deploy the newly activated site 
        
            1864
                    result = celery_tasks.copy_site.delay(repo.route) 
        
            1865
                if had_site and not repo.has_site: 
        
            1867
                    # Remove the site 
        
            1868
                    result = celery_tasks.delete_site.delay(repo.route) 
        
            1869
                if repo.has_site == 2 or (had_site == 2 and had_site != repo.has_site): 
        
            1871
                    # Deploy all other sites which were destroyed by the primary site 
        
            1872
                    for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=1): 
        
            1873
                        result = celery_tasks.copy_site.delay(other_repo.route) 
        
            1874
                return flask.redirect(f"/{username}/{repository}/settings", 303) 
        
            1876
            @repositories.route("/<username>/<repository>/settings/add-label", methods=["POST"]) 
        
            1879
            def repository_settings_add_label(username, repository): 
        
            1880
                if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1881
                    flask.abort(401) 
        
            1882
                repo_data = db.session.get(Repo, f"/{username}/{repository}") 
        
            1884
                label = Label(repo_data, flask.request.form.get("label"), flask.request.form.get("colour")) 
        
            1886
                db.session.add(label) 
        
            1887
                db.session.commit() 
        
            1888
                return flask.redirect(f"/{username}/{repository}/settings", 303) 
        
            1890
            @repositories.route("/<username>/<repository>/settings/delete-label", methods=["POST"]) 
        
            1893
            def repository_settings_delete_label(username, repository): 
        
            1894
                if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1895
                    flask.abort(401) 
        
            1896
                repo_data = db.session.get(Repo, f"/{username}/{repository}") 
        
            1898
                label = db.session.get(Label, flask.request.form.get("id")) 
        
            1900
                db.session.delete(label) 
        
            1902
                db.session.commit() 
        
            1903
                return flask.redirect(f"/{username}/{repository}/settings", 303) 
        
            1905
            @repositories.route("/<username>/<repository>/settings/edit-label", methods=["POST"]) 
        
            1908
            def repository_settings_edit_label(username, repository): 
        
            1909
                if get_permission_level(flask.session.get("username"), username, repository) != 2: 
        
            1910
                    flask.abort(401) 
        
            1911
                repo_data = db.session.get(Repo, f"/{username}/{repository}") 
        
            1913
                label = db.session.get(Label, flask.request.form.get("id")) 
        
            1915
                label.name = flask.request.form.get("label") 
        
            1917
                label.colour_hex = flask.request.form.get("colour") 
        
            1918
                db.session.commit() 
        
            1920
                return flask.redirect(f"/{username}/{repository}/settings", 303) 
        
            1922
            @repositories.route("/<username>/<repository>/settings/delete", methods=["POST"]) 
        
            1925
            def repository_settings_delete(username, repository): 
        
            1926
                if username != flask.session.get("username"): 
        
            1927
                    flask.abort(401) 
        
            1928
                repo = db.session.get(Repo, f"/{username}/{repository}") 
        
            1930
                if not repo: 
        
            1932
                    flask.abort(404) 
        
            1933
                user = db.session.get(User, flask.session.get("username")) 
        
            1935
                if not bcrypt.check_password_hash(user.password_hashed, flask.request.form.get("password")): 
        
            1937
                    flask.flash(_("Incorrect password"), category="error") 
        
            1938
                    flask.abort(401) 
        
            1939
                if repo.has_site: 
        
            1941
                    celery_tasks.delete_site.delay(repo.route) 
        
            1942
                db.session.delete(repo) 
        
            1944
                db.session.commit() 
        
            1945
                shutil.rmtree(os.path.join(config.REPOS_PATH, username, repository)) 
        
            1947
                return flask.redirect(f"/{username}", 303) 
        
            1949
            @app.errorhandler(404) 
        
            1952
            def e404(error): 
        
            1953
                return flask.render_template("errors/not-found.html"), 404 
        
            1954
            @app.errorhandler(401) 
        
            1957
            def e401(error): 
        
            1958
                return flask.render_template("errors/unauthorised.html"), 401 
        
            1959
            @app.errorhandler(403) 
        
            1962
            def e403(error): 
        
            1963
                return flask.render_template("errors/forbidden.html"), 403 
        
            1964
            @app.errorhandler(418) 
        
            1967
            def e418(error): 
        
            1968
                return flask.render_template("errors/teapot.html"), 418 
        
            1969
            @app.errorhandler(405) 
        
            1972
            def e405(error): 
        
            1973
                return flask.render_template("errors/method-not-allowed.html"), 405 
        
            1974
            @app.errorhandler(500) 
        
            1977
            def e500(error): 
        
            1978
                return flask.render_template("errors/server-error.html"), 500 
        
            1979
            @app.errorhandler(400) 
        
            1982
            def e400(error): 
        
            1983
                return flask.render_template("errors/bad-request.html"), 400 
        
            1984
            @app.errorhandler(410) 
        
            1987
            def e410(error): 
        
            1988
                return flask.render_template("errors/gone.html"), 410 
        
            1989
            @app.errorhandler(415) 
        
            1992
            def e415(error): 
        
            1993
                return flask.render_template("errors/media-type.html"), 415 
        
            1994
            if __name__ == "__main__": 
        
            1997
                app.run(debug=True, port=8080, host="0.0.0.0") 
        
            1998
            app.register_blueprint(repositories) 
        
            2000