Python script, ASCII text executable
        
            1
            import os 
        
            2
            import shutil 
        
            3
            import random 
        
            4
            import subprocess 
        
            5
            import platform 
        
            6
            import git 
        
            7
            import mimetypes 
        
            8
            import magic 
        
            9
            import flask 
        
            10
            import cairosvg 
        
            11
            from functools import wraps 
        
            12
            from datetime import datetime 
        
            13
            from enum import Enum 
        
            14
            from cairosvg import svg2png 
        
            15
            from flask_sqlalchemy import SQLAlchemy 
        
            16
            from flask_bcrypt import Bcrypt 
        
            17
            from markupsafe import escape, Markup 
        
            18
            from flask_migrate import Migrate 
        
            19
            from PIL import Image 
        
            20
            from flask_httpauth import HTTPBasicAuth 
        
            21
            from celery import Celery, Task 
        
            22
            import config 
        
            23
            import celeryIntegration 
        
            24
            app = flask.Flask(__name__) 
        
            26
            app.config.from_mapping( 
        
            27
                CELERY=dict( 
        
            28
                    broker_url=config.REDIS_URI, 
        
            29
                    result_backend=config.REDIS_URI, 
        
            30
                    task_ignore_result=True, 
        
            31
                ), 
        
            32
            ) 
        
            33
            worker = celeryIntegration.initCeleryApp(app) 
        
            34
            auth = HTTPBasicAuth() 
        
            36
            app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 
        
            38
            app.config["SECRET_KEY"] = config.DB_PASSWORD 
        
            39
            app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 
        
            40
            db = SQLAlchemy(app) 
        
            41
            bcrypt = Bcrypt(app) 
        
            42
            migrate = Migrate(app, db) 
        
            43
            from models import * 
        
            44
            def gitCommand(repo, data, *args): 
        
            47
                if not os.path.isdir(repo): 
        
            48
                    raise FileNotFoundError("Repo not found") 
        
            49
                env = os.environ.copy() 
        
            50
                command = ["git", *args] 
        
            52
                proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, 
        
            54
                                        stdin=subprocess.PIPE) 
        
            55
                print(command) 
        
            56
                if data: 
        
            58
                    proc.stdin.write(data) 
        
            59
                out, err = proc.communicate() 
        
            61
                return out 
        
            62
            def onlyChars(string, chars): 
        
            65
                for i in string: 
        
            66
                    if i not in chars: 
        
            67
                        return False 
        
            68
                return True 
        
            69
            def getPermissionLevel(loggedIn, username, repository): 
        
            72
                user = User.query.filter_by(username=loggedIn).first() 
        
            73
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            74
                if user and repo: 
        
            76
                    permission = RepoAccess.query.filter_by(user=user, repo=repo).first() 
        
            77
                    if permission: 
        
            78
                        return permission.accessLevel 
        
            79
                return None 
        
            81
            def getVisibility(username, repository): 
        
            84
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            85
                if repo: 
        
            87
                    return repo.visibility 
        
            88
                return None 
        
            90
            def getFavourite(loggedIn, username, repository): 
        
            93
                print(loggedIn, username, repository) 
        
            94
                relationship = RepoFavourite.query.filter_by(userUsername=loggedIn, repoRoute=f"/{username}/{repository}").first() 
        
            95
                return relationship 
        
            96
            import gitHTTP 
        
            99
            import jinjaUtils 
        
            100
            def humanSize(value, decimals=2, scale=1024, 
        
            103
                          units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")): 
        
            104
                for unit in units: 
        
            105
                    if value < scale: 
        
            106
                        break 
        
            107
                    value /= scale 
        
            108
                if int(value) == value: 
        
            109
                    # do not return decimals, if the value is already round 
        
            110
                    return int(value), unit 
        
            111
                return round(value * 10 ** decimals) / 10 ** decimals, unit 
        
            112
            def guessMIME(path): 
        
            115
                if os.path.isdir(path): 
        
            116
                    mimetype = "inode/directory" 
        
            117
                elif magic.from_file(path, mime=True): 
        
            118
                    mimetype = magic.from_file(path, mime=True) 
        
            119
                else: 
        
            120
                    mimetype = "application/octet-stream" 
        
            121
                return mimetype 
        
            122
            def convertToHTML(path): 
        
            125
                with open(path, "r") as f: 
        
            126
                    contents = f.read() 
        
            127
                return contents 
        
            128
            repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 
        
            131
            @app.context_processor 
        
            134
            def default(): 
        
            135
                username = flask.session.get("username") 
        
            136
                userObject = User.query.filter_by(username=username).first() 
        
            138
                return { 
        
            140
                    "loggedInUser": username, 
        
            141
                    "userObject": userObject, 
        
            142
                    "Notification": Notification, 
        
            143
                    "unread": UserNotification.query.filter_by(userUsername=username).filter(UserNotification.attentionLevel > 0).count() 
        
            144
                } 
        
            145
            @app.route("/") 
        
            148
            def main(): 
        
            149
                return flask.render_template("home.html") 
        
            150
            @app.route("/about/") 
        
            153
            def about(): 
        
            154
                return flask.render_template("about.html", platform=platform) 
        
            155
            @app.route("/settings/", methods=["GET", "POST"]) 
        
            158
            def settings(): 
        
            159
                if not flask.session.get("username"): 
        
            160
                    flask.abort(401) 
        
            161
                if flask.request.method == "GET": 
        
            162
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            163
                    return flask.render_template("user-settings.html", user=user) 
        
            165
                else: 
        
            166
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            167
                    user.displayName = flask.request.form["displayname"] 
        
            169
                    user.URL = flask.request.form["url"] 
        
            170
                    user.company = flask.request.form["company"] 
        
            171
                    user.companyURL = flask.request.form["companyurl"] 
        
            172
                    user.location = flask.request.form["location"] 
        
            173
                    user.showMail = flask.request.form.get("showmail", user.showMail) 
        
            174
                    db.session.commit() 
        
            176
                    flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success") 
        
            178
                    return flask.redirect(f"/{flask.session.get('username')}", code=303) 
        
            179
            @app.route("/favourites/", methods=["GET", "POST"]) 
        
            182
            def favourites(): 
        
            183
                if not flask.session.get("username"): 
        
            184
                    flask.abort(401) 
        
            185
                if flask.request.method == "GET": 
        
            186
                    relationships = RepoFavourite.query.filter_by(userUsername=flask.session.get("username")) 
        
            187
                    return flask.render_template("favourites.html", favourites=relationships) 
        
            189
            @app.route("/notifications/", methods=["GET", "POST"]) 
        
            192
            def notifications(): 
        
            193
                if not flask.session.get("username"): 
        
            194
                    flask.abort(401) 
        
            195
                if flask.request.method == "GET": 
        
            196
                    return flask.render_template("notifications.html", notifications=UserNotification.query.filter_by(userUsername=flask.session.get("username"))) 
        
            197
            @app.route("/accounts/", methods=["GET", "POST"]) 
        
            200
            def login(): 
        
            201
                if flask.request.method == "GET": 
        
            202
                    return flask.render_template("login.html") 
        
            203
                else: 
        
            204
                    if "login" in flask.request.form: 
        
            205
                        username = flask.request.form["username"] 
        
            206
                        password = flask.request.form["password"] 
        
            207
                        user = User.query.filter_by(username=username).first() 
        
            209
                        if user and bcrypt.check_password_hash(user.passwordHashed, password): 
        
            211
                            flask.session["username"] = user.username 
        
            212
                            flask.flash( 
        
            213
                                Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"), 
        
            214
                                category="success") 
        
            215
                            return flask.redirect("/", code=303) 
        
            216
                        elif not user: 
        
            217
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"), 
        
            218
                                        category="alert") 
        
            219
                            return flask.render_template("login.html") 
        
            220
                        else: 
        
            221
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"), 
        
            222
                                        category="error") 
        
            223
                            return flask.render_template("login.html") 
        
            224
                    if "signup" in flask.request.form: 
        
            225
                        username = flask.request.form["username"] 
        
            226
                        password = flask.request.form["password"] 
        
            227
                        password2 = flask.request.form["password2"] 
        
            228
                        email = flask.request.form.get("email") 
        
            229
                        email2 = flask.request.form.get("email2")  # repeat email is a honeypot 
        
            230
                        name = flask.request.form.get("name") 
        
            231
                        if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            233
                            flask.flash(Markup( 
        
            234
                                "<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            235
                                        category="error") 
        
            236
                            return flask.render_template("login.html") 
        
            237
                        if username in config.RESERVED_NAMES: 
        
            239
                            flask.flash( 
        
            240
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"), 
        
            241
                                category="error") 
        
            242
                            return flask.render_template("login.html") 
        
            243
                        userCheck = User.query.filter_by(username=username).first() 
        
            245
                        if userCheck: 
        
            246
                            flask.flash( 
        
            247
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"), 
        
            248
                                category="error") 
        
            249
                            return flask.render_template("login.html") 
        
            250
                        if password2 != password: 
        
            252
                            flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"), 
        
            253
                                        category="error") 
        
            254
                            return flask.render_template("login.html") 
        
            255
                        user = User(username, password, email, name) 
        
            257
                        db.session.add(user) 
        
            258
                        db.session.commit() 
        
            259
                        flask.session["username"] = user.username 
        
            260
                        flask.flash(Markup( 
        
            261
                            f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"), 
        
            262
                                    category="success") 
        
            263
                        return flask.redirect("/", code=303) 
        
            264
            @app.route("/newrepo/", methods=["GET", "POST"]) 
        
            267
            def newRepo(): 
        
            268
                if not flask.session.get("username"): 
        
            269
                    flask.abort(401) 
        
            270
                if flask.request.method == "GET": 
        
            271
                    return flask.render_template("new-repo.html") 
        
            272
                else: 
        
            273
                    name = flask.request.form["name"] 
        
            274
                    visibility = int(flask.request.form["visibility"]) 
        
            275
                    if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            277
                        flask.flash(Markup( 
        
            278
                                "<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            279
                                category="error") 
        
            280
                        return flask.render_template("new-repo.html") 
        
            281
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            283
                    repo = Repo(user, name, visibility) 
        
            285
                    db.session.add(repo) 
        
            286
                    db.session.commit() 
        
            287
                    if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)): 
        
            289
                        subprocess.run(["git", "init", repo.name], 
        
            290
                                       cwd=os.path.join(config.REPOS_PATH, flask.session.get("username"))) 
        
            291
                    flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"), 
        
            293
                                category="success") 
        
            294
                    return flask.redirect(repo.route, code=303) 
        
            295
            @app.route("/logout") 
        
            298
            def logout(): 
        
            299
                flask.session.clear() 
        
            300
                flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info") 
        
            301
                return flask.redirect("/", code=303) 
        
            302
            @app.route("/<username>/", methods=["GET", "POST"]) 
        
            305
            def userProfile(username): 
        
            306
                oldRelationship = UserFollow.query.filter_by(followerUsername=flask.session.get("username"), followedUsername=username).first() 
        
            307
                if flask.request.method == "GET": 
        
            308
                    user = User.query.filter_by(username=username).first() 
        
            309
                    match flask.request.args.get("action"): 
        
            310
                        case "repositories": 
        
            311
                            repos = Repo.query.filter_by(ownerName=username, visibility=2) 
        
            312
                            return flask.render_template("user-profile-repositories.html", user=user, repos=repos, relationship=oldRelationship) 
        
            313
                        case "followers": 
        
            314
                            return flask.render_template("user-profile-followers.html", user=user, relationship=oldRelationship) 
        
            315
                        case "follows": 
        
            316
                            return flask.render_template("user-profile-follows.html", user=user, relationship=oldRelationship) 
        
            317
                        case _: 
        
            318
                            return flask.render_template("user-profile-overview.html", user=user, relationship=oldRelationship) 
        
            319
                elif flask.request.method == "POST": 
        
            321
                    match flask.request.args.get("action"): 
        
            322
                        case "follow": 
        
            323
                            if oldRelationship: 
        
            324
                                db.session.delete(oldRelationship) 
        
            325
                            else: 
        
            326
                                relationship = UserFollow( 
        
            327
                                        flask.session.get("username"), 
        
            328
                                        username 
        
            329
                                ) 
        
            330
                                db.session.add(relationship) 
        
            331
                            db.session.commit() 
        
            333
                            return flask.redirect("?", code=303) 
        
            334
            @app.route("/<username>/<repository>/") 
        
            337
            def repositoryIndex(username, repository): 
        
            338
                return flask.redirect("./tree", code=302) 
        
            339
            @app.route("/info/<username>/avatar") 
        
            342
            def userAvatar(username): 
        
            343
                serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) 
        
            344
                if not os.path.exists(serverUserdataLocation): 
        
            346
                    return flask.render_template("not-found.html"), 404 
        
            347
                return flask.send_from_directory(serverUserdataLocation, "avatar.png") 
        
            349
            @app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 
        
            352
            def repositoryRaw(username, repository, branch, subpath): 
        
            353
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            354
                                                                                  repository) is not None): 
        
            355
                    flask.abort(403) 
        
            356
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            358
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            360
                if not os.path.exists(serverRepoLocation): 
        
            362
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            363
                    return flask.render_template("not-found.html"), 404 
        
            364
                repo = git.Repo(serverRepoLocation) 
        
            366
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            367
                if not repoData.defaultBranch: 
        
            368
                    if repo.heads: 
        
            369
                        repoData.defaultBranch = repo.heads[0].name 
        
            370
                    else: 
        
            371
                        return flask.render_template("empty.html", 
        
            372
                                                     remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            373
                if not branch: 
        
            374
                    branch = repoData.defaultBranch 
        
            375
                    return flask.redirect(f"./{branch}", code=302) 
        
            376
                if branch.startswith("tag:"): 
        
            378
                    ref = f"tags/{branch[4:]}" 
        
            379
                elif branch.startswith("~"): 
        
            380
                    ref = branch[1:] 
        
            381
                else: 
        
            382
                    ref = f"heads/{branch}" 
        
            383
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            385
                try: 
        
            387
                    repo.git.checkout("-f", ref) 
        
            388
                except git.exc.GitCommandError: 
        
            389
                    return flask.render_template("not-found.html"), 404 
        
            390
                return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath)) 
        
            392
            @repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 
        
            395
            @repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 
        
            396
            @repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 
        
            397
            def repositoryTree(username, repository, branch, subpath): 
        
            398
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            399
                                                                                  repository) is not None): 
        
            400
                    flask.abort(403) 
        
            401
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            403
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            405
                if not os.path.exists(serverRepoLocation): 
        
            407
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            408
                    return flask.render_template("not-found.html"), 404 
        
            409
                repo = git.Repo(serverRepoLocation) 
        
            411
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            412
                if not repoData.defaultBranch: 
        
            413
                    if repo.heads: 
        
            414
                        repoData.defaultBranch = repo.heads[0].name 
        
            415
                    else: 
        
            416
                        return flask.render_template("empty.html", 
        
            417
                                                     remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            418
                if not branch: 
        
            419
                    branch = repoData.defaultBranch 
        
            420
                    return flask.redirect(f"./{branch}", code=302) 
        
            421
                if branch.startswith("tag:"): 
        
            423
                    ref = f"tags/{branch[4:]}" 
        
            424
                elif branch.startswith("~"): 
        
            425
                    ref = branch[1:] 
        
            426
                else: 
        
            427
                    ref = f"heads/{branch}" 
        
            428
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            430
                try: 
        
            432
                    repo.git.checkout("-f", ref) 
        
            433
                except git.exc.GitCommandError: 
        
            434
                    return flask.render_template("not-found.html"), 404 
        
            435
                branches = repo.heads 
        
            437
                allRefs = [] 
        
            439
                for ref in repo.heads: 
        
            440
                    allRefs.append((ref, "head")) 
        
            441
                for ref in repo.tags: 
        
            442
                    allRefs.append((ref, "tag")) 
        
            443
                if os.path.isdir(os.path.join(serverRepoLocation, subpath)): 
        
            445
                    files = [] 
        
            446
                    blobs = [] 
        
            447
                    for entry in os.listdir(os.path.join(serverRepoLocation, subpath)): 
        
            449
                        if not os.path.basename(entry) == ".git": 
        
            450
                            files.append(os.path.join(subpath, entry)) 
        
            451
                    infos = [] 
        
            453
                    for file in files: 
        
            455
                        path = os.path.join(serverRepoLocation, file) 
        
            456
                        mimetype = guessMIME(path) 
        
            457
                        text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode() 
        
            459
                        sha = text.split("\n")[0] 
        
            461
                        identifier = f"/{username}/{repository}/{sha}" 
        
            462
                        lastCommit = Commit.query.filter_by(identifier=identifier).first() 
        
            463
                        info = { 
        
            465
                            "name": os.path.basename(file), 
        
            466
                            "serverPath": path, 
        
            467
                            "relativePath": file, 
        
            468
                            "link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 
        
            469
                            "size": humanSize(os.path.getsize(path)), 
        
            470
                            "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 
        
            471
                            "commit": lastCommit, 
        
            472
                            "shaSize": 7, 
        
            473
                        } 
        
            474
                        specialIcon = config.matchIcon(os.path.basename(file)) 
        
            476
                        if specialIcon: 
        
            477
                            info["icon"] = specialIcon 
        
            478
                        elif os.path.isdir(path): 
        
            479
                            info["icon"] = config.folderIcon 
        
            480
                        elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            481
                            info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            482
                        else: 
        
            483
                            info["icon"] = config.unknownIcon 
        
            484
                        if os.path.isdir(path): 
        
            486
                            infos.insert(0, info) 
        
            487
                        else: 
        
            488
                            infos.append(info) 
        
            489
                    return flask.render_template( 
        
            491
                            "repo-tree.html", 
        
            492
                            username=username, 
        
            493
                            repository=repository, 
        
            494
                            files=infos, 
        
            495
                            subpath=os.path.join("/", subpath), 
        
            496
                            branches=allRefs, 
        
            497
                            current=branch, 
        
            498
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            499
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            500
                    ) 
        
            501
                else: 
        
            502
                    path = os.path.join(serverRepoLocation, subpath) 
        
            503
                    if not os.path.exists(path): 
        
            505
                        return flask.render_template("not-found.html"), 404 
        
            506
                    mimetype = guessMIME(path) 
        
            508
                    mode = mimetype.split("/", 1)[0] 
        
            509
                    size = humanSize(os.path.getsize(path)) 
        
            510
                    specialIcon = config.matchIcon(os.path.basename(path)) 
        
            512
                    if specialIcon: 
        
            513
                        icon = specialIcon 
        
            514
                    elif os.path.isdir(path): 
        
            515
                        icon = config.folderIcon 
        
            516
                    elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            517
                        icon = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            518
                    else: 
        
            519
                        icon = config.unknownIcon 
        
            520
                    contents = None 
        
            522
                    if mode == "text": 
        
            523
                        contents = convertToHTML(path) 
        
            524
                    return flask.render_template( 
        
            526
                            "repo-file.html", 
        
            527
                            username=username, 
        
            528
                            repository=repository, 
        
            529
                            file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 
        
            530
                            branches=allRefs, 
        
            531
                            current=branch, 
        
            532
                            mode=mode, 
        
            533
                            mimetype=mimetype, 
        
            534
                            detailedtype=magic.from_file(path), 
        
            535
                            size=size, 
        
            536
                            icon=icon, 
        
            537
                            subpath=os.path.join("/", subpath), 
        
            538
                            basename=os.path.basename(path), 
        
            539
                            contents=contents, 
        
            540
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            541
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            542
                    ) 
        
            543
            @repositories.route("/<username>/<repository>/forum/") 
        
            546
            def repositoryForum(username, repository): 
        
            547
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            548
                                                                                  repository) is not None): 
        
            549
                    flask.abort(403) 
        
            550
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            552
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            554
                if not os.path.exists(serverRepoLocation): 
        
            556
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            557
                    return flask.render_template("not-found.html"), 404 
        
            558
                repo = git.Repo(serverRepoLocation) 
        
            560
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            561
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            562
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            563
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            564
                return flask.render_template( 
        
            566
                        "repo-forum.html", 
        
            567
                        username=username, 
        
            568
                        repository=repository, 
        
            569
                        repoData=repoData, 
        
            570
                        relationships=relationships, 
        
            571
                        repo=repo, 
        
            572
                        userRelationship=userRelationship, 
        
            573
                        Post=Post, 
        
            574
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            575
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            576
                ) 
        
            577
            @repositories.route("/<username>/<repository>/forum/new", methods=["POST"]) 
        
            580
            def repositoryForumAdd(username, repository): 
        
            581
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            582
                                                                                  repository) is not None): 
        
            583
                    flask.abort(403) 
        
            584
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            586
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            588
                if not os.path.exists(serverRepoLocation): 
        
            590
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            591
                    return flask.render_template("not-found.html"), 404 
        
            592
                repo = git.Repo(serverRepoLocation) 
        
            594
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            595
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            596
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            597
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            598
                post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"]) 
        
            600
                db.session.add(post) 
        
            602
                db.session.commit() 
        
            603
                return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=post.number), code=303) 
        
            605
            @repositories.route("/<username>/<repository>/forum/<int:postID>") 
        
            608
            def repositoryForumThread(username, repository, postID): 
        
            609
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            610
                                                                                  repository) is not None): 
        
            611
                    flask.abort(403) 
        
            612
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            614
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            616
                if not os.path.exists(serverRepoLocation): 
        
            618
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            619
                    return flask.render_template("not-found.html"), 404 
        
            620
                repo = git.Repo(serverRepoLocation) 
        
            622
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            623
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            624
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            625
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            626
                return flask.render_template( 
        
            628
                        "repo-forum-thread.html", 
        
            629
                        username=username, 
        
            630
                        repository=repository, 
        
            631
                        repoData=repoData, 
        
            632
                        relationships=relationships, 
        
            633
                        repo=repo, 
        
            634
                        userRelationship=userRelationship, 
        
            635
                        Post=Post, 
        
            636
                        postID=postID, 
        
            637
                        maxPostNesting=4, 
        
            638
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            639
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            640
                ) 
        
            641
            @repositories.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"]) 
        
            644
            def repositoryForumReply(username, repository, postID): 
        
            645
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            646
                                                                                  repository) is not None): 
        
            647
                    flask.abort(403) 
        
            648
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            650
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            652
                if not os.path.exists(serverRepoLocation): 
        
            654
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            655
                    return flask.render_template("not-found.html"), 404 
        
            656
                repo = git.Repo(serverRepoLocation) 
        
            658
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            659
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            660
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            661
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            662
                if not user: 
        
            663
                    flask.abort(401) 
        
            664
                parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            666
                post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"]) 
        
            667
                db.session.add(post) 
        
            669
                post.updateDate() 
        
            670
                db.session.commit() 
        
            671
                return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=postID), code=303) 
        
            673
            @app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1}) 
        
            676
            @app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1}) 
        
            677
            @app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0}) 
        
            678
            def repositoryForumVote(username, repository, postID, score): 
        
            679
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            680
                                                                                  repository) is not None): 
        
            681
                    flask.abort(403) 
        
            682
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            684
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            686
                if not os.path.exists(serverRepoLocation): 
        
            688
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            689
                    return flask.render_template("not-found.html"), 404 
        
            690
                repo = git.Repo(serverRepoLocation) 
        
            692
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            693
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            694
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            695
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            696
                if not user: 
        
            697
                    flask.abort(401) 
        
            698
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            700
                if score: 
        
            702
                    oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            703
                    if oldRelationship: 
        
            704
                        if score == oldRelationship.voteScore: 
        
            705
                            db.session.delete(oldRelationship) 
        
            706
                            post.voteSum -= oldRelationship.voteScore 
        
            707
                        else: 
        
            708
                            post.voteSum -= oldRelationship.voteScore 
        
            709
                            post.voteSum += score 
        
            710
                            oldRelationship.voteScore = score 
        
            711
                    else: 
        
            712
                        relationship = PostVote(user, post, score) 
        
            713
                        post.voteSum += score 
        
            714
                        db.session.add(relationship) 
        
            715
                    db.session.commit() 
        
            717
                userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            719
                response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0)) 
        
            720
                response.content_type = "text/plain" 
        
            721
                return response 
        
            723
            @app.route("/<username>/<repository>/favourite") 
        
            726
            def repositoryFavourite(username, repository): 
        
            727
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            728
                                                                                  repository) is not None): 
        
            729
                    flask.abort(403) 
        
            730
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            732
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            734
                if not os.path.exists(serverRepoLocation): 
        
            736
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            737
                    return flask.render_template("not-found.html"), 404 
        
            738
                repo = git.Repo(serverRepoLocation) 
        
            740
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            741
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            742
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            743
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            744
                if not user: 
        
            745
                    flask.abort(401) 
        
            746
                oldRelationship = RepoFavourite.query.filter_by(userUsername=user.username, repoRoute=repoData.route).first() 
        
            748
                if oldRelationship: 
        
            749
                    db.session.delete(oldRelationship) 
        
            750
                else: 
        
            751
                    relationship = RepoFavourite(user, repoData) 
        
            752
                    db.session.add(relationship) 
        
            753
                db.session.commit() 
        
            755
                return flask.redirect(flask.url_for("favourites"), code=303) 
        
            757
            @repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 
        
            760
            def repositoryUsers(username, repository): 
        
            761
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            762
                                                                                  repository) is not None): 
        
            763
                    flask.abort(403) 
        
            764
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            766
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            768
                if not os.path.exists(serverRepoLocation): 
        
            770
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            771
                    return flask.render_template("not-found.html"), 404 
        
            772
                repo = git.Repo(serverRepoLocation) 
        
            774
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            775
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            776
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            777
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            778
                if flask.request.method == "GET": 
        
            780
                    return flask.render_template( 
        
            781
                            "repo-users.html", 
        
            782
                            username=username, 
        
            783
                            repository=repository, 
        
            784
                            repoData=repoData, 
        
            785
                            relationships=relationships, 
        
            786
                            repo=repo, 
        
            787
                            userRelationship=userRelationship, 
        
            788
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            789
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            790
                    ) 
        
            791
                else: 
        
            792
                    if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            793
                        flask.abort(401) 
        
            794
                    if flask.request.form.get("new-username"): 
        
            796
                        # Create new relationship 
        
            797
                        newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first() 
        
            798
                        relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level")) 
        
            799
                        db.session.add(relationship) 
        
            800
                        db.session.commit() 
        
            801
                    if flask.request.form.get("update-username"): 
        
            802
                        # Create new relationship 
        
            803
                        updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first() 
        
            804
                        relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first() 
        
            805
                        if flask.request.form.get("update-level") == -1: 
        
            806
                            relationship.delete() 
        
            807
                        else: 
        
            808
                            relationship.accessLevel = flask.request.form.get("update-level") 
        
            809
                        db.session.commit() 
        
            810
                    return flask.redirect(app.url_for(".repositoryUsers", username=username, repository=repository)) 
        
            812
            @repositories.route("/<username>/<repository>/branches/") 
        
            815
            def repositoryBranches(username, repository): 
        
            816
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            817
                                                                                  repository) is not None): 
        
            818
                    flask.abort(403) 
        
            819
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            821
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            823
                if not os.path.exists(serverRepoLocation): 
        
            825
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            826
                    return flask.render_template("not-found.html"), 404 
        
            827
                repo = git.Repo(serverRepoLocation) 
        
            829
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            830
                return flask.render_template( 
        
            832
                        "repo-branches.html", 
        
            833
                        username=username, 
        
            834
                        repository=repository, 
        
            835
                        repoData=repoData, 
        
            836
                        repo=repo, 
        
            837
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            838
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            839
                ) 
        
            840
            @repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 
        
            843
            @repositories.route("/<username>/<repository>/log/<branch>/") 
        
            844
            def repositoryLog(username, repository, branch): 
        
            845
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            846
                                                                                  repository) is not None): 
        
            847
                    flask.abort(403) 
        
            848
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            850
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            852
                if not os.path.exists(serverRepoLocation): 
        
            854
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            855
                    return flask.render_template("not-found.html"), 404 
        
            856
                repo = git.Repo(serverRepoLocation) 
        
            858
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            859
                if not repoData.defaultBranch: 
        
            860
                    if repo.heads: 
        
            861
                        repoData.defaultBranch = repo.heads[0].name 
        
            862
                    else: 
        
            863
                        return flask.render_template("empty.html", 
        
            864
                                                     remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            865
                if not branch: 
        
            866
                    branch = repoData.defaultBranch 
        
            867
                    return flask.redirect(f"./{branch}", code=302) 
        
            868
                if branch.startswith("tag:"): 
        
            870
                    ref = f"tags/{branch[4:]}" 
        
            871
                elif branch.startswith("~"): 
        
            872
                    ref = branch[1:] 
        
            873
                else: 
        
            874
                    ref = f"heads/{branch}" 
        
            875
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            877
                try: 
        
            879
                    repo.git.checkout("-f", ref) 
        
            880
                except git.exc.GitCommandError: 
        
            881
                    return flask.render_template("not-found.html"), 404 
        
            882
                branches = repo.heads 
        
            884
                allRefs = [] 
        
            886
                for ref in repo.heads: 
        
            887
                    allRefs.append((ref, "head")) 
        
            888
                for ref in repo.tags: 
        
            889
                    allRefs.append((ref, "tag")) 
        
            890
                commitList = [f"/{username}/{repository}/{sha}" for sha in gitCommand(serverRepoLocation, None, "log", "--format='%H'").decode().split("\n")] 
        
            892
                commits = Commit.query.filter(Commit.identifier.in_(commitList)) 
        
            894
                return flask.render_template( 
        
            896
                        "repo-log.html", 
        
            897
                        username=username, 
        
            898
                        repository=repository, 
        
            899
                        branches=allRefs, 
        
            900
                        current=branch, 
        
            901
                        repoData=repoData, 
        
            902
                        repo=repo, 
        
            903
                        commits=commits, 
        
            904
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            905
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            906
                ) 
        
            907
            @repositories.route("/<username>/<repository>/settings/") 
        
            910
            def repositorySettings(username, repository): 
        
            911
                if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            912
                    flask.abort(401) 
        
            913
                return flask.render_template("repo-settings.html", username=username, repository=repository) 
        
            915
            @app.errorhandler(404) 
        
            918
            def e404(error): 
        
            919
                return flask.render_template("not-found.html"), 404 
        
            920
            @app.errorhandler(401) 
        
            923
            def e401(error): 
        
            924
                return flask.render_template("unauthorised.html"), 401 
        
            925
            @app.errorhandler(403) 
        
            928
            def e403(error): 
        
            929
                return flask.render_template("forbidden.html"), 403 
        
            930
            @app.errorhandler(418) 
        
            933
            def e418(error): 
        
            934
                return flask.render_template("teapot.html"), 418 
        
            935
            @app.errorhandler(405) 
        
            938
            def e405(error): 
        
            939
                return flask.render_template("method-not-allowed.html"), 405 
        
            940
            if __name__ == "__main__": 
        
            943
                app.run(debug=True, port=8080, host="0.0.0.0") 
        
            944
            app.register_blueprint(repositories) 
        
            947