By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 app.py

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