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
            import celeryTasks 
        
            46
            def gitCommand(repo, data, *args): 
        
            49
                if not os.path.isdir(repo): 
        
            50
                    raise FileNotFoundError("Repo not found") 
        
            51
                env = os.environ.copy() 
        
            52
                command = ["git", *args] 
        
            54
                proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, 
        
            56
                                        stdin=subprocess.PIPE) 
        
            57
                print(command) 
        
            58
                if data: 
        
            60
                    proc.stdin.write(data) 
        
            61
                out, err = proc.communicate() 
        
            63
                return out 
        
            64
            def onlyChars(string, chars): 
        
            67
                for i in string: 
        
            68
                    if i not in chars: 
        
            69
                        return False 
        
            70
                return True 
        
            71
            def getPermissionLevel(loggedIn, username, repository): 
        
            74
                user = User.query.filter_by(username=loggedIn).first() 
        
            75
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            76
                if user and repo: 
        
            78
                    permission = RepoAccess.query.filter_by(user=user, repo=repo).first() 
        
            79
                    if permission: 
        
            80
                        return permission.accessLevel 
        
            81
                return None 
        
            83
            def getVisibility(username, repository): 
        
            86
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            87
                if repo: 
        
            89
                    return repo.visibility 
        
            90
                return None 
        
            92
            def getFavourite(loggedIn, username, repository): 
        
            95
                print(loggedIn, username, repository) 
        
            96
                relationship = RepoFavourite.query.filter_by(userUsername=loggedIn, repoRoute=f"/{username}/{repository}").first() 
        
            97
                return relationship 
        
            98
            import gitHTTP 
        
            101
            import jinjaUtils 
        
            102
            def humanSize(value, decimals=2, scale=1024, 
        
            105
                          units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")): 
        
            106
                for unit in units: 
        
            107
                    if value < scale: 
        
            108
                        break 
        
            109
                    value /= scale 
        
            110
                if int(value) == value: 
        
            111
                    # do not return decimals, if the value is already round 
        
            112
                    return int(value), unit 
        
            113
                return round(value * 10 ** decimals) / 10 ** decimals, unit 
        
            114
            def guessMIME(path): 
        
            117
                if os.path.isdir(path): 
        
            118
                    mimetype = "inode/directory" 
        
            119
                elif magic.from_file(path, mime=True): 
        
            120
                    mimetype = magic.from_file(path, mime=True) 
        
            121
                else: 
        
            122
                    mimetype = "application/octet-stream" 
        
            123
                return mimetype 
        
            124
            def convertToHTML(path): 
        
            127
                with open(path, "r") as f: 
        
            128
                    contents = f.read() 
        
            129
                return contents 
        
            130
            repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/") 
        
            133
            @app.context_processor 
        
            136
            def default(): 
        
            137
                username = flask.session.get("username") 
        
            138
                userObject = User.query.filter_by(username=username).first() 
        
            140
                return { 
        
            142
                    "loggedInUser": username, 
        
            143
                    "userObject": userObject, 
        
            144
                    "Notification": Notification, 
        
            145
                    "unread": UserNotification.query.filter_by(userUsername=username).filter(UserNotification.attentionLevel > 0).count() 
        
            146
                } 
        
            147
            @app.route("/") 
        
            150
            def main(): 
        
            151
                return flask.render_template("home.html") 
        
            152
            @app.route("/about/") 
        
            155
            def about(): 
        
            156
                return flask.render_template("about.html", platform=platform) 
        
            157
            @app.route("/settings/", methods=["GET", "POST"]) 
        
            160
            def settings(): 
        
            161
                if not flask.session.get("username"): 
        
            162
                    flask.abort(401) 
        
            163
                if flask.request.method == "GET": 
        
            164
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            165
                    return flask.render_template("user-settings.html", user=user) 
        
            167
                else: 
        
            168
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            169
                    user.displayName = flask.request.form["displayname"] 
        
            171
                    user.URL = flask.request.form["url"] 
        
            172
                    user.company = flask.request.form["company"] 
        
            173
                    user.companyURL = flask.request.form["companyurl"] 
        
            174
                    user.location = flask.request.form["location"] 
        
            175
                    user.showMail = flask.request.form.get("showmail", user.showMail) 
        
            176
                    db.session.commit() 
        
            178
                    flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success") 
        
            180
                    return flask.redirect(f"/{flask.session.get('username')}", code=303) 
        
            181
            @app.route("/favourites/", methods=["GET", "POST"]) 
        
            184
            def favourites(): 
        
            185
                if not flask.session.get("username"): 
        
            186
                    flask.abort(401) 
        
            187
                if flask.request.method == "GET": 
        
            188
                    relationships = RepoFavourite.query.filter_by(userUsername=flask.session.get("username")) 
        
            189
                    return flask.render_template("favourites.html", favourites=relationships) 
        
            191
            @app.route("/notifications/", methods=["GET", "POST"]) 
        
            194
            def notifications(): 
        
            195
                if not flask.session.get("username"): 
        
            196
                    flask.abort(401) 
        
            197
                if flask.request.method == "GET": 
        
            198
                    return flask.render_template("notifications.html", notifications=UserNotification.query.filter_by(userUsername=flask.session.get("username"))) 
        
            199
            @app.route("/accounts/", methods=["GET", "POST"]) 
        
            202
            def login(): 
        
            203
                if flask.request.method == "GET": 
        
            204
                    return flask.render_template("login.html") 
        
            205
                else: 
        
            206
                    if "login" in flask.request.form: 
        
            207
                        username = flask.request.form["username"] 
        
            208
                        password = flask.request.form["password"] 
        
            209
                        user = User.query.filter_by(username=username).first() 
        
            211
                        if user and bcrypt.check_password_hash(user.passwordHashed, password): 
        
            213
                            flask.session["username"] = user.username 
        
            214
                            flask.flash( 
        
            215
                                Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"), 
        
            216
                                category="success") 
        
            217
                            return flask.redirect("/", code=303) 
        
            218
                        elif not user: 
        
            219
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"), 
        
            220
                                        category="alert") 
        
            221
                            return flask.render_template("login.html") 
        
            222
                        else: 
        
            223
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"), 
        
            224
                                        category="error") 
        
            225
                            return flask.render_template("login.html") 
        
            226
                    if "signup" in flask.request.form: 
        
            227
                        username = flask.request.form["username"] 
        
            228
                        password = flask.request.form["password"] 
        
            229
                        password2 = flask.request.form["password2"] 
        
            230
                        email = flask.request.form.get("email") 
        
            231
                        email2 = flask.request.form.get("email2")  # repeat email is a honeypot 
        
            232
                        name = flask.request.form.get("name") 
        
            233
                        if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            235
                            flask.flash(Markup( 
        
            236
                                "<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            237
                                        category="error") 
        
            238
                            return flask.render_template("login.html") 
        
            239
                        if username in config.RESERVED_NAMES: 
        
            241
                            flask.flash( 
        
            242
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"), 
        
            243
                                category="error") 
        
            244
                            return flask.render_template("login.html") 
        
            245
                        userCheck = User.query.filter_by(username=username).first() 
        
            247
                        if userCheck: 
        
            248
                            flask.flash( 
        
            249
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"), 
        
            250
                                category="error") 
        
            251
                            return flask.render_template("login.html") 
        
            252
                        if password2 != password: 
        
            254
                            flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"), 
        
            255
                                        category="error") 
        
            256
                            return flask.render_template("login.html") 
        
            257
                        user = User(username, password, email, name) 
        
            259
                        db.session.add(user) 
        
            260
                        db.session.commit() 
        
            261
                        flask.session["username"] = user.username 
        
            262
                        flask.flash(Markup( 
        
            263
                            f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"), 
        
            264
                                    category="success") 
        
            265
                        return flask.redirect("/", code=303) 
        
            266
            @app.route("/newrepo/", methods=["GET", "POST"]) 
        
            269
            def newRepo(): 
        
            270
                if not flask.session.get("username"): 
        
            271
                    flask.abort(401) 
        
            272
                if flask.request.method == "GET": 
        
            273
                    return flask.render_template("new-repo.html") 
        
            274
                else: 
        
            275
                    name = flask.request.form["name"] 
        
            276
                    visibility = int(flask.request.form["visibility"]) 
        
            277
                    if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            279
                        flask.flash(Markup( 
        
            280
                                "<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            281
                                category="error") 
        
            282
                        return flask.render_template("new-repo.html") 
        
            283
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            285
                    repo = Repo(user, name, visibility) 
        
            287
                    db.session.add(repo) 
        
            288
                    db.session.commit() 
        
            289
                    if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)): 
        
            291
                        subprocess.run(["git", "init", repo.name], 
        
            292
                                       cwd=os.path.join(config.REPOS_PATH, flask.session.get("username"))) 
        
            293
                    flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"), 
        
            295
                                category="success") 
        
            296
                    return flask.redirect(repo.route, code=303) 
        
            297
            @app.route("/logout") 
        
            300
            def logout(): 
        
            301
                flask.session.clear() 
        
            302
                flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info") 
        
            303
                return flask.redirect("/", code=303) 
        
            304
            @app.route("/<username>/", methods=["GET", "POST"]) 
        
            307
            def userProfile(username): 
        
            308
                oldRelationship = UserFollow.query.filter_by(followerUsername=flask.session.get("username"), followedUsername=username).first() 
        
            309
                if flask.request.method == "GET": 
        
            310
                    user = User.query.filter_by(username=username).first() 
        
            311
                    match flask.request.args.get("action"): 
        
            312
                        case "repositories": 
        
            313
                            repos = Repo.query.filter_by(ownerName=username, visibility=2) 
        
            314
                            return flask.render_template("user-profile-repositories.html", user=user, repos=repos, relationship=oldRelationship) 
        
            315
                        case "followers": 
        
            316
                            return flask.render_template("user-profile-followers.html", user=user, relationship=oldRelationship) 
        
            317
                        case "follows": 
        
            318
                            return flask.render_template("user-profile-follows.html", user=user, relationship=oldRelationship) 
        
            319
                        case _: 
        
            320
                            return flask.render_template("user-profile-overview.html", user=user, relationship=oldRelationship) 
        
            321
                elif flask.request.method == "POST": 
        
            323
                    match flask.request.args.get("action"): 
        
            324
                        case "follow": 
        
            325
                            if username == flask.session.get("username"): 
        
            326
                                flask.abort(403) 
        
            327
                            if oldRelationship: 
        
            328
                                db.session.delete(oldRelationship) 
        
            329
                            else: 
        
            330
                                relationship = UserFollow( 
        
            331
                                        flask.session.get("username"), 
        
            332
                                        username 
        
            333
                                ) 
        
            334
                                db.session.add(relationship) 
        
            335
                                db.session.commit() 
        
            336
                                user = db.session.get(User, username) 
        
            338
                                author = db.session.get(User, flask.session.get("username")) 
        
            339
                                notification = Notification({"type": "update", "version": "0.0.0"}) 
        
            340
                                db.session.add(notification) 
        
            341
                                db.session.commit() 
        
            342
                                result = celeryTasks.sendNotification.delay(notification.id, [username], 1) 
        
            344
                                flask.flash(f"Sending notification in task {result.id}", "success") 
        
            345
                            db.session.commit() 
        
            347
                            return flask.redirect("?", code=303) 
        
            348
            @app.route("/<username>/<repository>/") 
        
            351
            def repositoryIndex(username, repository): 
        
            352
                return flask.redirect("./tree", code=302) 
        
            353
            @app.route("/info/<username>/avatar") 
        
            356
            def userAvatar(username): 
        
            357
                serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) 
        
            358
                if not os.path.exists(serverUserdataLocation): 
        
            360
                    return flask.render_template("not-found.html"), 404 
        
            361
                return flask.send_from_directory(serverUserdataLocation, "avatar.png") 
        
            363
            @app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 
        
            366
            def repositoryRaw(username, repository, branch, subpath): 
        
            367
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            368
                                                                                  repository) is not None): 
        
            369
                    flask.abort(403) 
        
            370
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            372
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            374
                if not os.path.exists(serverRepoLocation): 
        
            376
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            377
                    return flask.render_template("not-found.html"), 404 
        
            378
                repo = git.Repo(serverRepoLocation) 
        
            380
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            381
                if not repoData.defaultBranch: 
        
            382
                    if repo.heads: 
        
            383
                        repoData.defaultBranch = repo.heads[0].name 
        
            384
                    else: 
        
            385
                        return flask.render_template("empty.html", 
        
            386
                                                     remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            387
                if not branch: 
        
            388
                    branch = repoData.defaultBranch 
        
            389
                    return flask.redirect(f"./{branch}", code=302) 
        
            390
                if branch.startswith("tag:"): 
        
            392
                    ref = f"tags/{branch[4:]}" 
        
            393
                elif branch.startswith("~"): 
        
            394
                    ref = branch[1:] 
        
            395
                else: 
        
            396
                    ref = f"heads/{branch}" 
        
            397
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            399
                try: 
        
            401
                    repo.git.checkout("-f", ref) 
        
            402
                except git.exc.GitCommandError: 
        
            403
                    return flask.render_template("not-found.html"), 404 
        
            404
                return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath)) 
        
            406
            @repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 
        
            409
            @repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 
        
            410
            @repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 
        
            411
            def repositoryTree(username, repository, branch, subpath): 
        
            412
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            413
                                                                                  repository) is not None): 
        
            414
                    flask.abort(403) 
        
            415
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            417
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            419
                if not os.path.exists(serverRepoLocation): 
        
            421
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            422
                    return flask.render_template("not-found.html"), 404 
        
            423
                repo = git.Repo(serverRepoLocation) 
        
            425
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            426
                if not repoData.defaultBranch: 
        
            427
                    if repo.heads: 
        
            428
                        repoData.defaultBranch = repo.heads[0].name 
        
            429
                    else: 
        
            430
                        return flask.render_template("empty.html", 
        
            431
                                                     remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            432
                if not branch: 
        
            433
                    branch = repoData.defaultBranch 
        
            434
                    return flask.redirect(f"./{branch}", code=302) 
        
            435
                if branch.startswith("tag:"): 
        
            437
                    ref = f"tags/{branch[4:]}" 
        
            438
                elif branch.startswith("~"): 
        
            439
                    ref = branch[1:] 
        
            440
                else: 
        
            441
                    ref = f"heads/{branch}" 
        
            442
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            444
                try: 
        
            446
                    repo.git.checkout("-f", ref) 
        
            447
                except git.exc.GitCommandError: 
        
            448
                    return flask.render_template("not-found.html"), 404 
        
            449
                branches = repo.heads 
        
            451
                allRefs = [] 
        
            453
                for ref in repo.heads: 
        
            454
                    allRefs.append((ref, "head")) 
        
            455
                for ref in repo.tags: 
        
            456
                    allRefs.append((ref, "tag")) 
        
            457
                if os.path.isdir(os.path.join(serverRepoLocation, subpath)): 
        
            459
                    files = [] 
        
            460
                    blobs = [] 
        
            461
                    for entry in os.listdir(os.path.join(serverRepoLocation, subpath)): 
        
            463
                        if not os.path.basename(entry) == ".git": 
        
            464
                            files.append(os.path.join(subpath, entry)) 
        
            465
                    infos = [] 
        
            467
                    for file in files: 
        
            469
                        path = os.path.join(serverRepoLocation, file) 
        
            470
                        mimetype = guessMIME(path) 
        
            471
                        text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode() 
        
            473
                        sha = text.split("\n")[0] 
        
            475
                        identifier = f"/{username}/{repository}/{sha}" 
        
            476
                        lastCommit = Commit.query.filter_by(identifier=identifier).first() 
        
            477
                        info = { 
        
            479
                            "name": os.path.basename(file), 
        
            480
                            "serverPath": path, 
        
            481
                            "relativePath": file, 
        
            482
                            "link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 
        
            483
                            "size": humanSize(os.path.getsize(path)), 
        
            484
                            "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 
        
            485
                            "commit": lastCommit, 
        
            486
                            "shaSize": 7, 
        
            487
                        } 
        
            488
                        specialIcon = config.matchIcon(os.path.basename(file)) 
        
            490
                        if specialIcon: 
        
            491
                            info["icon"] = specialIcon 
        
            492
                        elif os.path.isdir(path): 
        
            493
                            info["icon"] = config.folderIcon 
        
            494
                        elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            495
                            info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            496
                        else: 
        
            497
                            info["icon"] = config.unknownIcon 
        
            498
                        if os.path.isdir(path): 
        
            500
                            infos.insert(0, info) 
        
            501
                        else: 
        
            502
                            infos.append(info) 
        
            503
                    return flask.render_template( 
        
            505
                            "repo-tree.html", 
        
            506
                            username=username, 
        
            507
                            repository=repository, 
        
            508
                            files=infos, 
        
            509
                            subpath=os.path.join("/", subpath), 
        
            510
                            branches=allRefs, 
        
            511
                            current=branch, 
        
            512
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            513
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            514
                    ) 
        
            515
                else: 
        
            516
                    path = os.path.join(serverRepoLocation, subpath) 
        
            517
                    if not os.path.exists(path): 
        
            519
                        return flask.render_template("not-found.html"), 404 
        
            520
                    mimetype = guessMIME(path) 
        
            522
                    mode = mimetype.split("/", 1)[0] 
        
            523
                    size = humanSize(os.path.getsize(path)) 
        
            524
                    specialIcon = config.matchIcon(os.path.basename(path)) 
        
            526
                    if specialIcon: 
        
            527
                        icon = specialIcon 
        
            528
                    elif os.path.isdir(path): 
        
            529
                        icon = config.folderIcon 
        
            530
                    elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            531
                        icon = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            532
                    else: 
        
            533
                        icon = config.unknownIcon 
        
            534
                    contents = None 
        
            536
                    if mode == "text": 
        
            537
                        contents = convertToHTML(path) 
        
            538
                    return flask.render_template( 
        
            540
                            "repo-file.html", 
        
            541
                            username=username, 
        
            542
                            repository=repository, 
        
            543
                            file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 
        
            544
                            branches=allRefs, 
        
            545
                            current=branch, 
        
            546
                            mode=mode, 
        
            547
                            mimetype=mimetype, 
        
            548
                            detailedtype=magic.from_file(path), 
        
            549
                            size=size, 
        
            550
                            icon=icon, 
        
            551
                            subpath=os.path.join("/", subpath), 
        
            552
                            basename=os.path.basename(path), 
        
            553
                            contents=contents, 
        
            554
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            555
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            556
                    ) 
        
            557
            @repositories.route("/<username>/<repository>/forum/") 
        
            560
            def repositoryForum(username, repository): 
        
            561
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            562
                                                                                  repository) is not None): 
        
            563
                    flask.abort(403) 
        
            564
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            566
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            568
                if not os.path.exists(serverRepoLocation): 
        
            570
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            571
                    return flask.render_template("not-found.html"), 404 
        
            572
                repo = git.Repo(serverRepoLocation) 
        
            574
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            575
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            576
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            577
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            578
                return flask.render_template( 
        
            580
                        "repo-forum.html", 
        
            581
                        username=username, 
        
            582
                        repository=repository, 
        
            583
                        repoData=repoData, 
        
            584
                        relationships=relationships, 
        
            585
                        repo=repo, 
        
            586
                        userRelationship=userRelationship, 
        
            587
                        Post=Post, 
        
            588
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            589
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            590
                ) 
        
            591
            @repositories.route("/<username>/<repository>/forum/new", methods=["POST"]) 
        
            594
            def repositoryForumAdd(username, repository): 
        
            595
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            596
                                                                                  repository) is not None): 
        
            597
                    flask.abort(403) 
        
            598
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            600
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            602
                if not os.path.exists(serverRepoLocation): 
        
            604
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            605
                    return flask.render_template("not-found.html"), 404 
        
            606
                repo = git.Repo(serverRepoLocation) 
        
            608
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            609
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            610
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            611
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            612
                post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"]) 
        
            614
                db.session.add(post) 
        
            616
                db.session.commit() 
        
            617
                return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=post.number), code=303) 
        
            619
            @repositories.route("/<username>/<repository>/forum/<int:postID>") 
        
            622
            def repositoryForumThread(username, repository, postID): 
        
            623
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            624
                                                                                  repository) is not None): 
        
            625
                    flask.abort(403) 
        
            626
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            628
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            630
                if not os.path.exists(serverRepoLocation): 
        
            632
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            633
                    return flask.render_template("not-found.html"), 404 
        
            634
                repo = git.Repo(serverRepoLocation) 
        
            636
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            637
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            638
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            639
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            640
                return flask.render_template( 
        
            642
                        "repo-forum-thread.html", 
        
            643
                        username=username, 
        
            644
                        repository=repository, 
        
            645
                        repoData=repoData, 
        
            646
                        relationships=relationships, 
        
            647
                        repo=repo, 
        
            648
                        userRelationship=userRelationship, 
        
            649
                        Post=Post, 
        
            650
                        postID=postID, 
        
            651
                        maxPostNesting=4, 
        
            652
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            653
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            654
                ) 
        
            655
            @repositories.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"]) 
        
            658
            def repositoryForumReply(username, repository, postID): 
        
            659
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            660
                                                                                  repository) is not None): 
        
            661
                    flask.abort(403) 
        
            662
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            664
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            666
                if not os.path.exists(serverRepoLocation): 
        
            668
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            669
                    return flask.render_template("not-found.html"), 404 
        
            670
                repo = git.Repo(serverRepoLocation) 
        
            672
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            673
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            674
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            675
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            676
                if not user: 
        
            677
                    flask.abort(401) 
        
            678
                parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            680
                post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"]) 
        
            681
                db.session.add(post) 
        
            683
                post.updateDate() 
        
            684
                db.session.commit() 
        
            685
                return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=postID), code=303) 
        
            687
            @app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1}) 
        
            690
            @app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1}) 
        
            691
            @app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0}) 
        
            692
            def repositoryForumVote(username, repository, postID, score): 
        
            693
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            694
                                                                                  repository) is not None): 
        
            695
                    flask.abort(403) 
        
            696
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            698
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            700
                if not os.path.exists(serverRepoLocation): 
        
            702
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            703
                    return flask.render_template("not-found.html"), 404 
        
            704
                repo = git.Repo(serverRepoLocation) 
        
            706
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            707
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            708
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            709
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            710
                if not user: 
        
            711
                    flask.abort(401) 
        
            712
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            714
                if score: 
        
            716
                    oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            717
                    if oldRelationship: 
        
            718
                        if score == oldRelationship.voteScore: 
        
            719
                            db.session.delete(oldRelationship) 
        
            720
                            post.voteSum -= oldRelationship.voteScore 
        
            721
                        else: 
        
            722
                            post.voteSum -= oldRelationship.voteScore 
        
            723
                            post.voteSum += score 
        
            724
                            oldRelationship.voteScore = score 
        
            725
                    else: 
        
            726
                        relationship = PostVote(user, post, score) 
        
            727
                        post.voteSum += score 
        
            728
                        db.session.add(relationship) 
        
            729
                    db.session.commit() 
        
            731
                userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            733
                response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0)) 
        
            734
                response.content_type = "text/plain" 
        
            735
                return response 
        
            737
            @app.route("/<username>/<repository>/favourite") 
        
            740
            def repositoryFavourite(username, repository): 
        
            741
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            742
                                                                                  repository) is not None): 
        
            743
                    flask.abort(403) 
        
            744
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            746
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            748
                if not os.path.exists(serverRepoLocation): 
        
            750
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            751
                    return flask.render_template("not-found.html"), 404 
        
            752
                repo = git.Repo(serverRepoLocation) 
        
            754
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            755
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            756
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            757
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            758
                if not user: 
        
            759
                    flask.abort(401) 
        
            760
                oldRelationship = RepoFavourite.query.filter_by(userUsername=user.username, repoRoute=repoData.route).first() 
        
            762
                if oldRelationship: 
        
            763
                    db.session.delete(oldRelationship) 
        
            764
                else: 
        
            765
                    relationship = RepoFavourite(user, repoData) 
        
            766
                    db.session.add(relationship) 
        
            767
                db.session.commit() 
        
            769
                return flask.redirect(flask.url_for("favourites"), code=303) 
        
            771
            @repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 
        
            774
            def repositoryUsers(username, repository): 
        
            775
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            776
                                                                                  repository) is not None): 
        
            777
                    flask.abort(403) 
        
            778
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            780
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            782
                if not os.path.exists(serverRepoLocation): 
        
            784
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            785
                    return flask.render_template("not-found.html"), 404 
        
            786
                repo = git.Repo(serverRepoLocation) 
        
            788
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            789
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            790
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            791
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            792
                if flask.request.method == "GET": 
        
            794
                    return flask.render_template( 
        
            795
                            "repo-users.html", 
        
            796
                            username=username, 
        
            797
                            repository=repository, 
        
            798
                            repoData=repoData, 
        
            799
                            relationships=relationships, 
        
            800
                            repo=repo, 
        
            801
                            userRelationship=userRelationship, 
        
            802
                            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            803
                            isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            804
                    ) 
        
            805
                else: 
        
            806
                    if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            807
                        flask.abort(401) 
        
            808
                    if flask.request.form.get("new-username"): 
        
            810
                        # Create new relationship 
        
            811
                        newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first() 
        
            812
                        relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level")) 
        
            813
                        db.session.add(relationship) 
        
            814
                        db.session.commit() 
        
            815
                    if flask.request.form.get("update-username"): 
        
            816
                        # Create new relationship 
        
            817
                        updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first() 
        
            818
                        relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first() 
        
            819
                        if flask.request.form.get("update-level") == -1: 
        
            820
                            relationship.delete() 
        
            821
                        else: 
        
            822
                            relationship.accessLevel = flask.request.form.get("update-level") 
        
            823
                        db.session.commit() 
        
            824
                    return flask.redirect(app.url_for(".repositoryUsers", username=username, repository=repository)) 
        
            826
            @repositories.route("/<username>/<repository>/branches/") 
        
            829
            def repositoryBranches(username, repository): 
        
            830
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            831
                                                                                  repository) is not None): 
        
            832
                    flask.abort(403) 
        
            833
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            835
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            837
                if not os.path.exists(serverRepoLocation): 
        
            839
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            840
                    return flask.render_template("not-found.html"), 404 
        
            841
                repo = git.Repo(serverRepoLocation) 
        
            843
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            844
                return flask.render_template( 
        
            846
                        "repo-branches.html", 
        
            847
                        username=username, 
        
            848
                        repository=repository, 
        
            849
                        repoData=repoData, 
        
            850
                        repo=repo, 
        
            851
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            852
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            853
                ) 
        
            854
            @repositories.route("/<username>/<repository>/log/", defaults={"branch": None}) 
        
            857
            @repositories.route("/<username>/<repository>/log/<branch>/") 
        
            858
            def repositoryLog(username, repository, branch): 
        
            859
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            860
                                                                                  repository) is not None): 
        
            861
                    flask.abort(403) 
        
            862
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            864
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            866
                if not os.path.exists(serverRepoLocation): 
        
            868
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            869
                    return flask.render_template("not-found.html"), 404 
        
            870
                repo = git.Repo(serverRepoLocation) 
        
            872
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            873
                if not repoData.defaultBranch: 
        
            874
                    if repo.heads: 
        
            875
                        repoData.defaultBranch = repo.heads[0].name 
        
            876
                    else: 
        
            877
                        return flask.render_template("empty.html", 
        
            878
                                                     remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            879
                if not branch: 
        
            880
                    branch = repoData.defaultBranch 
        
            881
                    return flask.redirect(f"./{branch}", code=302) 
        
            882
                if branch.startswith("tag:"): 
        
            884
                    ref = f"tags/{branch[4:]}" 
        
            885
                elif branch.startswith("~"): 
        
            886
                    ref = branch[1:] 
        
            887
                else: 
        
            888
                    ref = f"heads/{branch}" 
        
            889
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            891
                try: 
        
            893
                    repo.git.checkout("-f", ref) 
        
            894
                except git.exc.GitCommandError: 
        
            895
                    return flask.render_template("not-found.html"), 404 
        
            896
                branches = repo.heads 
        
            898
                allRefs = [] 
        
            900
                for ref in repo.heads: 
        
            901
                    allRefs.append((ref, "head")) 
        
            902
                for ref in repo.tags: 
        
            903
                    allRefs.append((ref, "tag")) 
        
            904
                commitList = [f"/{username}/{repository}/{sha}" for sha in gitCommand(serverRepoLocation, None, "log", "--format='%H'").decode().split("\n")] 
        
            906
                commits = Commit.query.filter(Commit.identifier.in_(commitList)) 
        
            908
                return flask.render_template( 
        
            910
                        "repo-log.html", 
        
            911
                        username=username, 
        
            912
                        repository=repository, 
        
            913
                        branches=allRefs, 
        
            914
                        current=branch, 
        
            915
                        repoData=repoData, 
        
            916
                        repo=repo, 
        
            917
                        commits=commits, 
        
            918
                        remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}", 
        
            919
                        isFavourite=getFavourite(flask.session.get("username"), username, repository) 
        
            920
                ) 
        
            921
            @repositories.route("/<username>/<repository>/settings/") 
        
            924
            def repositorySettings(username, repository): 
        
            925
                if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            926
                    flask.abort(401) 
        
            927
                return flask.render_template("repo-settings.html", username=username, repository=repository) 
        
            929
            @app.errorhandler(404) 
        
            932
            def e404(error): 
        
            933
                return flask.render_template("not-found.html"), 404 
        
            934
            @app.errorhandler(401) 
        
            937
            def e401(error): 
        
            938
                return flask.render_template("unauthorised.html"), 401 
        
            939
            @app.errorhandler(403) 
        
            942
            def e403(error): 
        
            943
                return flask.render_template("forbidden.html"), 403 
        
            944
            @app.errorhandler(418) 
        
            947
            def e418(error): 
        
            948
                return flask.render_template("teapot.html"), 418 
        
            949
            @app.errorhandler(405) 
        
            952
            def e405(error): 
        
            953
                return flask.render_template("method-not-allowed.html"), 405 
        
            954
            if __name__ == "__main__": 
        
            957
                app.run(debug=True, port=8080, host="0.0.0.0") 
        
            958
            app.register_blueprint(repositories) 
        
            961