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 • 61.36 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.startswith("-") or username.endswith("-"):
364
flask.flash(Markup(
365
_("Usernames may not start or end with a hyphen")),
366
category="error")
367
return flask.render_template("login.html")
368
if username in config.RESERVED_NAMES:
369
flask.flash(
370
Markup(
371
_("Sorry, {username} is a system path").format(
372
username=username)),
373
category="error")
374
return flask.render_template("login.html")
375
376
if not username.islower():
377
if not name: # infer display name from the wanted username if not customised
378
display_name = username
379
username = username.lower()
380
flask.flash(Markup(
381
_("Usernames must be lowercase, so it's been converted automatically")),
382
category="info")
383
384
user_check = User.query.filter_by(username=username).first()
385
if user_check or email2: # make the honeypot look like a normal error
386
flask.flash(
387
Markup(
388
_(
389
"The username {username} is taken").format(
390
username=username)),
391
category="error")
392
return flask.render_template("login.html")
393
394
if password2 != password:
395
flask.flash(Markup(_(
396
"Make sure the passwords match")),
397
category="error")
398
return flask.render_template("login.html")
399
400
user = User(username, password, email, name)
401
db.session.add(user)
402
db.session.commit()
403
flask.session["username"] = user.username
404
flask.flash(Markup(
405
_(
406
"Successfully created and logged in as {username}").format(
407
username=username)),
408
category="success")
409
410
notification = Notification({"type": "welcome"})
411
db.session.add(notification)
412
db.session.commit()
413
414
return flask.redirect("/", code=303)
415
416
417
@app.route("/newrepo/", methods=["GET", "POST"])
418
def new_repo():
419
if not flask.session.get("username"):
420
flask.abort(401)
421
if flask.request.method == "GET":
422
return flask.render_template("new-repo.html")
423
else:
424
name = flask.request.form["name"]
425
visibility = int(flask.request.form["visibility"])
426
427
if not only_chars(name,
428
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
429
flask.flash(Markup(
430
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
431
"Repository names may only contain Latin alphabet, numbers, '-' and '_'")),
432
category="error")
433
return flask.render_template("new-repo.html")
434
435
user = User.query.filter_by(username=flask.session.get("username")).first()
436
437
repo = Repo(user, name, visibility)
438
db.session.add(repo)
439
db.session.commit()
440
441
flask.flash(Markup(_("Successfully created repository {name}").format(name=name)),
442
category="success")
443
return flask.redirect(repo.route, code=303)
444
445
446
@app.route("/logout")
447
def logout():
448
flask.session.clear()
449
flask.flash(Markup(
450
"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")),
451
category="info")
452
return flask.redirect("/", code=303)
453
454
455
@app.route("/<username>/", methods=["GET", "POST"])
456
def user_profile(username):
457
if db.session.get(User, username) is None:
458
flask.abort(404)
459
old_relationship = UserFollow.query.filter_by(
460
follower_username=flask.session.get("username"),
461
followed_username=username).first()
462
if flask.request.method == "GET":
463
user = User.query.filter_by(username=username).first()
464
match flask.request.args.get("action"):
465
case "repositories":
466
repos = Repo.query.filter_by(owner_name=username, visibility=2)
467
return flask.render_template("user-profile-repositories.html", user=user,
468
repos=repos,
469
relationship=old_relationship)
470
case "followers":
471
return flask.render_template("user-profile-followers.html", user=user,
472
relationship=old_relationship)
473
case "follows":
474
return flask.render_template("user-profile-follows.html", user=user,
475
relationship=old_relationship)
476
case _:
477
return flask.render_template("user-profile-overview.html", user=user,
478
relationship=old_relationship)
479
480
elif flask.request.method == "POST":
481
match flask.request.args.get("action"):
482
case "follow":
483
if username == flask.session.get("username"):
484
flask.abort(403)
485
if old_relationship:
486
db.session.delete(old_relationship)
487
else:
488
relationship = UserFollow(
489
flask.session.get("username"),
490
username
491
)
492
db.session.add(relationship)
493
db.session.commit()
494
495
user = db.session.get(User, username)
496
author = db.session.get(User, flask.session.get("username"))
497
notification = Notification({"type": "update", "version": "0.0.0"})
498
db.session.add(notification)
499
db.session.commit()
500
501
db.session.commit()
502
return flask.redirect("?", code=303)
503
504
505
@app.route("/<username>/<repository>/")
506
def repository_index(username, repository):
507
return flask.redirect("./tree", code=302)
508
509
510
@app.route("/info/<username>/avatar")
511
def user_avatar(username):
512
server_userdata_location = os.path.join(config.USERDATA_PATH, username)
513
if not os.path.exists(server_userdata_location):
514
return flask.render_template("not-found.html"), 404
515
516
return flask.send_from_directory(server_userdata_location, "avatar.png")
517
518
519
@app.route("/info/<username>/avatar", methods=["POST"])
520
def user_avatar_upload(username):
521
server_userdata_location = os.path.join(config.USERDATA_PATH, username)
522
523
if not os.path.exists(server_userdata_location):
524
flask.abort(404)
525
if not flask.session.get("username") == username:
526
flask.abort(403)
527
528
# Convert image to PNG
529
try:
530
image = Image.open(flask.request.files["avatar"])
531
except PIL.UnidentifiedImageError:
532
flask.abort(400)
533
image.save(os.path.join(server_userdata_location, "avatar.png"))
534
535
return flask.redirect(f"/{username}", code=303)
536
537
538
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
539
def repository_raw(username, repository, branch, subpath):
540
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
541
if not os.path.exists(server_repo_location):
542
app.logger.error(f"Cannot load {server_repo_location}")
543
flask.abort(404)
544
if not (get_visibility(username, repository) or get_permission_level(
545
flask.session.get("username"), username,
546
repository) is not None):
547
flask.abort(403)
548
549
app.logger.info(f"Loading {server_repo_location}")
550
551
if not os.path.exists(server_repo_location):
552
app.logger.error(f"Cannot load {server_repo_location}")
553
return flask.render_template("not-found.html"), 404
554
555
repo = git.Repo(server_repo_location)
556
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
557
if not repo_data.default_branch:
558
if repo.heads:
559
repo_data.default_branch = repo.heads[0].name
560
else:
561
return flask.render_template("empty.html",
562
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
563
if not branch:
564
branch = repo_data.default_branch
565
return flask.redirect(f"./{branch}", code=302)
566
567
if branch.startswith("tag:"):
568
ref = f"tags/{branch[4:]}"
569
elif branch.startswith("~"):
570
ref = branch[1:]
571
else:
572
ref = f"heads/{branch}"
573
574
ref = ref.replace("~", "/") # encode slashes for URL support
575
576
try:
577
repo.git.checkout("-f", ref)
578
except git.exc.GitCommandError:
579
return flask.render_template("not-found.html"), 404
580
581
return flask.send_from_directory(config.REPOS_PATH,
582
os.path.join(username, repository, subpath))
583
584
585
@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
586
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
587
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
588
def repository_tree(username, repository, branch, subpath):
589
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
590
if not os.path.exists(server_repo_location):
591
app.logger.error(f"Cannot load {server_repo_location}")
592
flask.abort(404)
593
if not (get_visibility(username, repository) or get_permission_level(
594
flask.session.get("username"), username,
595
repository) is not None):
596
flask.abort(403)
597
598
app.logger.info(f"Loading {server_repo_location}")
599
600
repo = git.Repo(server_repo_location)
601
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
602
if not repo_data.default_branch:
603
if repo.heads:
604
repo_data.default_branch = repo.heads[0].name
605
else:
606
return flask.render_template("empty.html",
607
remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
608
if not branch:
609
branch = repo_data.default_branch
610
return flask.redirect(f"./{branch}", code=302)
611
612
if branch.startswith("tag:"):
613
ref = f"tags/{branch[4:]}"
614
elif branch.startswith("~"):
615
ref = branch[1:]
616
else:
617
ref = f"heads/{branch}"
618
619
ref = ref.replace("~", "/") # encode slashes for URL support
620
621
try:
622
repo.git.checkout("-f", ref)
623
except git.exc.GitCommandError:
624
return flask.render_template("not-found.html"), 404
625
626
branches = repo.heads
627
628
all_refs = []
629
for ref in repo.heads:
630
all_refs.append((ref, "head"))
631
for ref in repo.tags:
632
all_refs.append((ref, "tag"))
633
634
if os.path.isdir(os.path.join(server_repo_location, subpath)):
635
files = []
636
blobs = []
637
638
for entry in os.listdir(os.path.join(server_repo_location, subpath)):
639
if not os.path.basename(entry) == ".git":
640
files.append(os.path.join(subpath, entry))
641
642
infos = []
643
644
for file in files:
645
path = os.path.join(server_repo_location, file)
646
mimetype = guess_mime(path)
647
648
text = git_command(server_repo_location, None, "log", "--format='%H\n'",
649
shlex.quote(file)).decode()
650
651
sha = text.split("\n")[0]
652
identifier = f"/{username}/{repository}/{sha}"
653
654
last_commit = db.session.get(Commit, identifier)
655
656
info = {
657
"name": os.path.basename(file),
658
"serverPath": path,
659
"relativePath": file,
660
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
661
"size": human_size(os.path.getsize(path)),
662
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
663
"commit": last_commit,
664
"shaSize": 7,
665
}
666
667
special_icon = config.match_icon(os.path.basename(file))
668
if special_icon:
669
info["icon"] = special_icon
670
elif os.path.isdir(path):
671
info["icon"] = config.folder_icon
672
elif mimetypes.guess_type(path)[0] in config.file_icons:
673
info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]]
674
else:
675
info["icon"] = config.unknown_icon
676
677
if os.path.isdir(path):
678
infos.insert(0, info)
679
else:
680
infos.append(info)
681
682
return flask.render_template(
683
"repo-tree.html",
684
username=username,
685
repository=repository,
686
files=infos,
687
subpath=os.path.join("/", subpath),
688
branches=all_refs,
689
current=branch,
690
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
691
is_favourite=get_favourite(flask.session.get("username"), username, repository),
692
repo_data=repo_data,
693
)
694
else:
695
path = os.path.join(server_repo_location, subpath)
696
697
if not os.path.exists(path):
698
return flask.render_template("not-found.html"), 404
699
700
mimetype = guess_mime(path)
701
mode = mimetype.split("/", 1)[0]
702
size = human_size(os.path.getsize(path))
703
704
special_icon = config.match_icon(os.path.basename(path))
705
if special_icon:
706
icon = special_icon
707
elif os.path.isdir(path):
708
icon = config.folder_icon
709
elif mimetypes.guess_type(path)[0] in config.file_icons:
710
icon = config.file_icons[mimetypes.guess_type(path)[0]]
711
else:
712
icon = config.unknown_icon
713
714
contents = None
715
if mode == "text":
716
contents = convert_to_html(path)
717
718
return flask.render_template(
719
"repo-file.html",
720
username=username,
721
repository=repository,
722
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
723
branches=all_refs,
724
current=branch,
725
mode=mode,
726
mimetype=mimetype,
727
detailedtype=magic.from_file(path),
728
size=size,
729
icon=icon,
730
subpath=os.path.join("/", subpath),
731
extension=pathlib.Path(path).suffix,
732
basename=os.path.basename(path),
733
contents=contents,
734
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
735
is_favourite=get_favourite(flask.session.get("username"), username, repository),
736
repo_data=repo_data,
737
)
738
739
740
@repositories.route("/<username>/<repository>/commit/<sha>")
741
def repository_commit(username, repository, sha):
742
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
743
if not os.path.exists(server_repo_location):
744
app.logger.error(f"Cannot load {server_repo_location}")
745
flask.abort(404)
746
if not (get_visibility(username, repository) or get_permission_level(
747
flask.session.get("username"), username,
748
repository) is not None):
749
flask.abort(403)
750
751
app.logger.info(f"Loading {server_repo_location}")
752
753
if not os.path.exists(server_repo_location):
754
app.logger.error(f"Cannot load {server_repo_location}")
755
return flask.render_template("not-found.html"), 404
756
757
repo = git.Repo(server_repo_location)
758
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
759
760
files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r",
761
"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1]
762
763
print(files)
764
765
return flask.render_template(
766
"repo-commit.html",
767
username=username,
768
repository=repository,
769
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
770
is_favourite=get_favourite(flask.session.get("username"), username, repository),
771
diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff",
772
str(sha) + "^!", "--", file).decode().split("\n") for
773
file in files},
774
data=db.session.get(Commit, f"/{username}/{repository}/{sha}"),
775
)
776
777
778
@repositories.route("/<username>/<repository>/forum/")
779
def repository_forum(username, repository):
780
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
781
if not os.path.exists(server_repo_location):
782
app.logger.error(f"Cannot load {server_repo_location}")
783
flask.abort(404)
784
if not (get_visibility(username, repository) or get_permission_level(
785
flask.session.get("username"), username,
786
repository) is not None):
787
flask.abort(403)
788
789
app.logger.info(f"Loading {server_repo_location}")
790
791
if not os.path.exists(server_repo_location):
792
app.logger.error(f"Cannot load {server_repo_location}")
793
return flask.render_template("not-found.html"), 404
794
795
repo = git.Repo(server_repo_location)
796
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
797
user = User.query.filter_by(username=flask.session.get("username")).first()
798
relationships = RepoAccess.query.filter_by(repo=repo_data)
799
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
800
801
return flask.render_template(
802
"repo-forum.html",
803
username=username,
804
repository=repository,
805
repo_data=repo_data,
806
relationships=relationships,
807
repo=repo,
808
user_relationship=user_relationship,
809
Post=Post,
810
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
811
is_favourite=get_favourite(flask.session.get("username"), username, repository),
812
default_branch=repo_data.default_branch
813
)
814
815
816
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
817
def repository_forum_topic(username, repository, id):
818
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
819
if not os.path.exists(server_repo_location):
820
app.logger.error(f"Cannot load {server_repo_location}")
821
flask.abort(404)
822
if not (get_visibility(username, repository) or get_permission_level(
823
flask.session.get("username"), username,
824
repository) is not None):
825
flask.abort(403)
826
827
app.logger.info(f"Loading {server_repo_location}")
828
829
if not os.path.exists(server_repo_location):
830
app.logger.error(f"Cannot load {server_repo_location}")
831
return flask.render_template("not-found.html"), 404
832
833
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
834
user = User.query.filter_by(username=flask.session.get("username")).first()
835
relationships = RepoAccess.query.filter_by(repo=repo_data)
836
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
837
838
post = Post.query.filter_by(id=id).first()
839
840
return flask.render_template(
841
"repo-topic.html",
842
username=username,
843
repository=repository,
844
repo_data=repo_data,
845
relationships=relationships,
846
user_relationship=user_relationship,
847
post=post,
848
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
849
is_favourite=get_favourite(flask.session.get("username"), username, repository),
850
default_branch=repo_data.default_branch
851
)
852
853
854
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
855
def repository_forum_new(username, repository):
856
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
857
if not os.path.exists(server_repo_location):
858
app.logger.error(f"Cannot load {server_repo_location}")
859
flask.abort(404)
860
if not (get_visibility(username, repository) or get_permission_level(
861
flask.session.get("username"), username,
862
repository) is not None):
863
flask.abort(403)
864
865
app.logger.info(f"Loading {server_repo_location}")
866
867
if not os.path.exists(server_repo_location):
868
app.logger.error(f"Cannot load {server_repo_location}")
869
return flask.render_template("not-found.html"), 404
870
871
repo = git.Repo(server_repo_location)
872
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
873
user = User.query.filter_by(username=flask.session.get("username")).first()
874
relationships = RepoAccess.query.filter_by(repo=repo_data)
875
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
876
877
post = Post(user, repo_data, None, flask.request.form["subject"],
878
flask.request.form["message"])
879
880
db.session.add(post)
881
db.session.commit()
882
883
return flask.redirect(
884
flask.url_for(".repository_forum_thread", username=username, repository=repository,
885
post_id=post.number),
886
code=303)
887
888
889
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
890
def repository_forum_thread(username, repository, post_id):
891
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
892
if not os.path.exists(server_repo_location):
893
app.logger.error(f"Cannot load {server_repo_location}")
894
flask.abort(404)
895
if not (get_visibility(username, repository) or get_permission_level(
896
flask.session.get("username"), username,
897
repository) is not None):
898
flask.abort(403)
899
900
app.logger.info(f"Loading {server_repo_location}")
901
902
if not os.path.exists(server_repo_location):
903
app.logger.error(f"Cannot load {server_repo_location}")
904
return flask.render_template("not-found.html"), 404
905
906
repo = git.Repo(server_repo_location)
907
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
908
user = User.query.filter_by(username=flask.session.get("username")).first()
909
relationships = RepoAccess.query.filter_by(repo=repo_data)
910
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
911
912
if user:
913
max_post_nesting = user.max_post_nesting
914
else:
915
max_post_nesting = 2
916
917
return flask.render_template(
918
"repo-forum-thread.html",
919
username=username,
920
repository=repository,
921
repo_data=repo_data,
922
relationships=relationships,
923
repo=repo,
924
Post=Post,
925
user_relationship=user_relationship,
926
post_id=post_id,
927
max_post_nesting=max_post_nesting,
928
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
929
is_favourite=get_favourite(flask.session.get("username"), username, repository),
930
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
931
has_permission=not ((not get_permission_level(flask.session.get("username"), username,
932
repository)) and db.session.get(Post,
933
f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")),
934
)
935
936
937
@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state",
938
methods=["POST"])
939
def repository_forum_change_state(username, repository, post_id):
940
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
941
if not os.path.exists(server_repo_location):
942
app.logger.error(f"Cannot load {server_repo_location}")
943
flask.abort(404)
944
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"):
945
flask.abort(403)
946
947
app.logger.info(f"Loading {server_repo_location}")
948
949
repo = git.Repo(server_repo_location)
950
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
951
user = User.query.filter_by(username=flask.session.get("username")).first()
952
relationships = RepoAccess.query.filter_by(repo=repo_data)
953
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
954
955
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
956
957
if not post:
958
flask.abort(404)
959
960
post.state = int(flask.request.form["new-state"])
961
962
db.session.commit()
963
964
return flask.redirect(
965
flask.url_for(".repository_forum_thread", username=username, repository=repository,
966
post_id=post_id),
967
code=303)
968
969
970
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
971
def repository_forum_reply(username, repository, post_id):
972
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
973
if not os.path.exists(server_repo_location):
974
app.logger.error(f"Cannot load {server_repo_location}")
975
flask.abort(404)
976
if not (get_visibility(username, repository) or get_permission_level(
977
flask.session.get("username"), username,
978
repository) is not None):
979
flask.abort(403)
980
981
app.logger.info(f"Loading {server_repo_location}")
982
983
if not os.path.exists(server_repo_location):
984
app.logger.error(f"Cannot load {server_repo_location}")
985
return flask.render_template("not-found.html"), 404
986
987
repo = git.Repo(server_repo_location)
988
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
989
user = User.query.filter_by(username=flask.session.get("username")).first()
990
relationships = RepoAccess.query.filter_by(repo=repo_data)
991
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
992
if not user:
993
flask.abort(401)
994
995
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
996
post = Post(user, repo_data, parent, flask.request.form["subject"],
997
flask.request.form["message"])
998
999
db.session.add(post)
1000
post.update_date()
1001
db.session.commit()
1002
1003
return flask.redirect(
1004
flask.url_for(".repository_forum_thread", username=username, repository=repository,
1005
post_id=post_id),
1006
code=303)
1007
1008
1009
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
1010
defaults={"score": 1})
1011
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
1012
defaults={"score": -1})
1013
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
1014
def repository_forum_vote(username, repository, post_id, score):
1015
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1016
if not os.path.exists(server_repo_location):
1017
app.logger.error(f"Cannot load {server_repo_location}")
1018
flask.abort(404)
1019
if not (get_visibility(username, repository) or get_permission_level(
1020
flask.session.get("username"), username,
1021
repository) is not None):
1022
flask.abort(403)
1023
1024
app.logger.info(f"Loading {server_repo_location}")
1025
1026
if not os.path.exists(server_repo_location):
1027
app.logger.error(f"Cannot load {server_repo_location}")
1028
return flask.render_template("not-found.html"), 404
1029
1030
repo = git.Repo(server_repo_location)
1031
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1032
user = User.query.filter_by(username=flask.session.get("username")).first()
1033
relationships = RepoAccess.query.filter_by(repo=repo_data)
1034
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1035
if not user:
1036
flask.abort(401)
1037
1038
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1039
1040
if score:
1041
old_relationship = PostVote.query.filter_by(user_username=user.username,
1042
post_identifier=post.identifier).first()
1043
if old_relationship:
1044
if score == old_relationship.vote_score:
1045
db.session.delete(old_relationship)
1046
post.vote_sum -= old_relationship.vote_score
1047
else:
1048
post.vote_sum -= old_relationship.vote_score
1049
post.vote_sum += score
1050
old_relationship.vote_score = score
1051
else:
1052
relationship = PostVote(user, post, score)
1053
post.vote_sum += score
1054
db.session.add(relationship)
1055
1056
db.session.commit()
1057
1058
user_vote = PostVote.query.filter_by(user_username=user.username,
1059
post_identifier=post.identifier).first()
1060
response = flask.make_response(
1061
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
1062
response.content_type = "text/plain"
1063
1064
return response
1065
1066
1067
@repositories.route("/<username>/<repository>/favourite")
1068
def repository_favourite(username, repository):
1069
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1070
if not os.path.exists(server_repo_location):
1071
app.logger.error(f"Cannot load {server_repo_location}")
1072
flask.abort(404)
1073
if not (get_visibility(username, repository) or get_permission_level(
1074
flask.session.get("username"), username,
1075
repository) is not None):
1076
flask.abort(403)
1077
1078
app.logger.info(f"Loading {server_repo_location}")
1079
1080
if not os.path.exists(server_repo_location):
1081
app.logger.error(f"Cannot load {server_repo_location}")
1082
return flask.render_template("not-found.html"), 404
1083
1084
repo = git.Repo(server_repo_location)
1085
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1086
user = User.query.filter_by(username=flask.session.get("username")).first()
1087
relationships = RepoAccess.query.filter_by(repo=repo_data)
1088
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1089
if not user:
1090
flask.abort(401)
1091
1092
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
1093
repo_route=repo_data.route).first()
1094
if old_relationship:
1095
db.session.delete(old_relationship)
1096
else:
1097
relationship = RepoFavourite(user, repo_data)
1098
db.session.add(relationship)
1099
1100
db.session.commit()
1101
1102
return flask.redirect(flask.url_for("favourites"), code=303)
1103
1104
1105
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
1106
def repository_users(username, repository):
1107
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1108
if not os.path.exists(server_repo_location):
1109
app.logger.error(f"Cannot load {server_repo_location}")
1110
flask.abort(404)
1111
if not (get_visibility(username, repository) or get_permission_level(
1112
flask.session.get("username"), username,
1113
repository) is not None):
1114
flask.abort(403)
1115
1116
app.logger.info(f"Loading {server_repo_location}")
1117
1118
if not os.path.exists(server_repo_location):
1119
app.logger.error(f"Cannot load {server_repo_location}")
1120
return flask.render_template("not-found.html"), 404
1121
1122
repo = git.Repo(server_repo_location)
1123
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1124
user = User.query.filter_by(username=flask.session.get("username")).first()
1125
relationships = RepoAccess.query.filter_by(repo=repo_data)
1126
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1127
1128
if flask.request.method == "GET":
1129
return flask.render_template(
1130
"repo-users.html",
1131
username=username,
1132
repository=repository,
1133
repo_data=repo_data,
1134
relationships=relationships,
1135
repo=repo,
1136
user_relationship=user_relationship,
1137
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1138
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1139
)
1140
else:
1141
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1142
flask.abort(401)
1143
1144
if flask.request.form.get("new-username"):
1145
# Create new relationship
1146
new_user = User.query.filter_by(
1147
username=flask.request.form.get("new-username")).first()
1148
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
1149
db.session.add(relationship)
1150
db.session.commit()
1151
if flask.request.form.get("update-username"):
1152
# Create new relationship
1153
updated_user = User.query.filter_by(
1154
username=flask.request.form.get("update-username")).first()
1155
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
1156
if flask.request.form.get("update-level") == -1:
1157
relationship.delete()
1158
else:
1159
relationship.access_level = flask.request.form.get("update-level")
1160
db.session.commit()
1161
1162
return flask.redirect(
1163
app.url_for(".repository_users", username=username, repository=repository))
1164
1165
1166
@repositories.route("/<username>/<repository>/branches/")
1167
def repository_branches(username, repository):
1168
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1169
if not os.path.exists(server_repo_location):
1170
app.logger.error(f"Cannot load {server_repo_location}")
1171
flask.abort(404)
1172
if not (get_visibility(username, repository) or get_permission_level(
1173
flask.session.get("username"), username,
1174
repository) is not None):
1175
flask.abort(403)
1176
1177
app.logger.info(f"Loading {server_repo_location}")
1178
1179
if not os.path.exists(server_repo_location):
1180
app.logger.error(f"Cannot load {server_repo_location}")
1181
return flask.render_template("not-found.html"), 404
1182
1183
repo = git.Repo(server_repo_location)
1184
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1185
1186
return flask.render_template(
1187
"repo-branches.html",
1188
username=username,
1189
repository=repository,
1190
repo_data=repo_data,
1191
repo=repo,
1192
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1193
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1194
)
1195
1196
1197
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1198
@repositories.route("/<username>/<repository>/log/<branch>/")
1199
def repository_log(username, repository, branch):
1200
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1201
if not os.path.exists(server_repo_location):
1202
app.logger.error(f"Cannot load {server_repo_location}")
1203
flask.abort(404)
1204
if not (get_visibility(username, repository) or get_permission_level(
1205
flask.session.get("username"), username,
1206
repository) is not None):
1207
flask.abort(403)
1208
1209
app.logger.info(f"Loading {server_repo_location}")
1210
1211
if not os.path.exists(server_repo_location):
1212
app.logger.error(f"Cannot load {server_repo_location}")
1213
return flask.render_template("not-found.html"), 404
1214
1215
repo = git.Repo(server_repo_location)
1216
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1217
if not repo_data.default_branch:
1218
if repo.heads:
1219
repo_data.default_branch = repo.heads[0].name
1220
else:
1221
return flask.render_template("empty.html",
1222
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1223
if not branch:
1224
branch = repo_data.default_branch
1225
return flask.redirect(f"./{branch}", code=302)
1226
1227
if branch.startswith("tag:"):
1228
ref = f"tags/{branch[4:]}"
1229
elif branch.startswith("~"):
1230
ref = branch[1:]
1231
else:
1232
ref = f"heads/{branch}"
1233
1234
ref = ref.replace("~", "/") # encode slashes for URL support
1235
1236
try:
1237
repo.git.checkout("-f", ref)
1238
except git.exc.GitCommandError:
1239
return flask.render_template("not-found.html"), 404
1240
1241
branches = repo.heads
1242
1243
all_refs = []
1244
for ref in repo.heads:
1245
all_refs.append((ref, "head"))
1246
for ref in repo.tags:
1247
all_refs.append((ref, "tag"))
1248
1249
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1250
git_command(server_repo_location, None, "log",
1251
"--format='%H'").decode().split("\n")]
1252
1253
commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc())
1254
page_number = flask.request.args.get("page", 1, type=int)
1255
if flask.session.get("username"):
1256
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
1257
else:
1258
default_page_length = 16
1259
page_length = flask.request.args.get("per_page", default_page_length, type=int)
1260
page_listing = db.paginate(commits, page=page_number, per_page=page_length)
1261
1262
if page_listing.has_next:
1263
next_page = page_listing.next_num
1264
else:
1265
next_page = None
1266
1267
if page_listing.has_prev:
1268
prev_page = page_listing.prev_num
1269
else:
1270
prev_page = None
1271
1272
return flask.render_template(
1273
"repo-log.html",
1274
username=username,
1275
repository=repository,
1276
branches=all_refs,
1277
current=branch,
1278
repo_data=repo_data,
1279
repo=repo,
1280
commits=page_listing,
1281
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1282
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1283
page_number=page_number,
1284
page_length=page_length,
1285
next_page=next_page,
1286
prev_page=prev_page,
1287
num_pages=page_listing.pages
1288
)
1289
1290
1291
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1292
def repository_prs(username, repository):
1293
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1294
if not os.path.exists(server_repo_location):
1295
app.logger.error(f"Cannot load {server_repo_location}")
1296
flask.abort(404)
1297
if not (get_visibility(username, repository) or get_permission_level(
1298
flask.session.get("username"), username,
1299
repository) is not None):
1300
flask.abort(403)
1301
1302
app.logger.info(f"Loading {server_repo_location}")
1303
1304
if not os.path.exists(server_repo_location):
1305
app.logger.error(f"Cannot load {server_repo_location}")
1306
return flask.render_template("not-found.html"), 404
1307
1308
if flask.request.method == "GET":
1309
repo = git.Repo(server_repo_location)
1310
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1311
user = User.query.filter_by(username=flask.session.get("username")).first()
1312
1313
return flask.render_template(
1314
"repo-prs.html",
1315
username=username,
1316
repository=repository,
1317
repo_data=repo_data,
1318
repo=repo,
1319
PullRequest=PullRequest,
1320
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1321
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1322
default_branch=repo_data.default_branch,
1323
branches=repo.branches
1324
)
1325
1326
else:
1327
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1328
head = flask.request.form.get("head")
1329
head_route = flask.request.form.get("headroute")
1330
base = flask.request.form.get("base")
1331
1332
if not head and base and head_route:
1333
return flask.redirect(".", 400)
1334
1335
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1336
base_repo = git.Repo(server_repo_location)
1337
print(head_repo)
1338
1339
if head not in head_repo.branches or base not in base_repo.branches:
1340
flask.flash(Markup(
1341
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1342
category="error")
1343
return flask.redirect(".", 303)
1344
1345
head_data = db.session.get(Repo, head_route)
1346
if not head_data.visibility:
1347
flask.flash(Markup(
1348
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1349
"Head can't be restricted")),
1350
category="error")
1351
return flask.redirect(".", 303)
1352
1353
pull_request = PullRequest(repo_data, head, head_data, base,
1354
db.session.get(User, flask.session["username"]))
1355
1356
db.session.add(pull_request)
1357
db.session.commit()
1358
1359
return flask.redirect(".", 303)
1360
1361
1362
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1363
def repository_prs_merge(username, repository):
1364
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1365
if not os.path.exists(server_repo_location):
1366
app.logger.error(f"Cannot load {server_repo_location}")
1367
flask.abort(404)
1368
if not (get_visibility(username, repository) or get_permission_level(
1369
flask.session.get("username"), username,
1370
repository) is not None):
1371
flask.abort(403)
1372
1373
if not get_permission_level(flask.session.get("username"), username, repository):
1374
flask.abort(401)
1375
1376
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1377
repo = git.Repo(server_repo_location)
1378
id = flask.request.form.get("id")
1379
1380
pull_request = db.session.get(PullRequest, id)
1381
1382
if pull_request:
1383
result = celery_tasks.merge_heads.delay(
1384
pull_request.head_route,
1385
pull_request.head_branch,
1386
pull_request.base_route,
1387
pull_request.base_branch,
1388
simulate=True
1389
)
1390
task_result = worker.AsyncResult(result.id)
1391
1392
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1393
# db.session.delete(pull_request)
1394
# db.session.commit()
1395
else:
1396
flask.abort(400)
1397
1398
1399
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1400
def repository_prs_merge_stage_two(username, repository, id):
1401
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1402
if not os.path.exists(server_repo_location):
1403
app.logger.error(f"Cannot load {server_repo_location}")
1404
flask.abort(404)
1405
if not (get_visibility(username, repository) or get_permission_level(
1406
flask.session.get("username"), username,
1407
repository) is not None):
1408
flask.abort(403)
1409
1410
if not get_permission_level(flask.session.get("username"), username, repository):
1411
flask.abort(401)
1412
1413
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1414
repo = git.Repo(server_repo_location)
1415
1416
pull_request = db.session.get(PullRequest, id)
1417
1418
if pull_request:
1419
result = celery_tasks.merge_heads.delay(
1420
pull_request.head_route,
1421
pull_request.head_branch,
1422
pull_request.base_route,
1423
pull_request.base_branch,
1424
simulate=False
1425
)
1426
task_result = worker.AsyncResult(result.id)
1427
1428
pull_request.state = 1
1429
db.session.commit()
1430
1431
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1432
# db.session.delete(pull_request)
1433
else:
1434
flask.abort(400)
1435
1436
1437
@app.route("/task/<task_id>")
1438
def task_monitor(task_id):
1439
task_result = worker.AsyncResult(task_id)
1440
if task_result.status == "FAILURE":
1441
app.logger.error(f"Task {task_id} failed")
1442
return flask.render_template("task-monitor.html", result=task_result), 500
1443
1444
return flask.render_template("task-monitor.html", result=task_result)
1445
1446
1447
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1448
def repository_prs_delete(username, repository):
1449
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1450
if not os.path.exists(server_repo_location):
1451
app.logger.error(f"Cannot load {server_repo_location}")
1452
flask.abort(404)
1453
if not (get_visibility(username, repository) or get_permission_level(
1454
flask.session.get("username"), username,
1455
repository) is not None):
1456
flask.abort(403)
1457
1458
if not get_permission_level(flask.session.get("username"), username, repository):
1459
flask.abort(401)
1460
1461
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1462
repo = git.Repo(server_repo_location)
1463
id = flask.request.form.get("id")
1464
1465
pull_request = db.session.get(PullRequest, id)
1466
1467
if pull_request:
1468
pull_request.state = 2
1469
db.session.commit()
1470
1471
return flask.redirect(".", 303)
1472
1473
1474
@repositories.route("/<username>/<repository>/settings/")
1475
def repository_settings(username, repository):
1476
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1477
flask.abort(401)
1478
1479
repo = git.Repo(os.path.join(config.REPOS_PATH, username, repository))
1480
1481
site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{repository}.{username}.{config.BASE_DOMAIN}/</code>")
1482
primary_site_link = Markup(f"<code>http{'s' if config.suggest_https else ''}://{username}.{config.BASE_DOMAIN}/</code>")
1483
1484
return flask.render_template("repo-settings.html", username=username, repository=repository,
1485
repo_data=db.session.get(Repo, f"/{username}/{repository}"),
1486
branches=[branch.name for branch in repo.branches],
1487
site_link=site_link, primary_site_link=primary_site_link)
1488
1489
1490
@repositories.route("/<username>/<repository>/settings/", methods=["POST"])
1491
def repository_settings_post(username, repository):
1492
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1493
flask.abort(401)
1494
1495
repo = db.session.get(Repo, f"/{username}/{repository}")
1496
1497
repo.visibility = flask.request.form.get("visibility", type=int)
1498
repo.info = flask.request.form.get("description")
1499
repo.default_branch = flask.request.form.get("default_branch")
1500
1501
# Update site settings
1502
had_site = repo.has_site
1503
old_branch = repo.site_branch
1504
if flask.request.form.get("site_branch"):
1505
repo.site_branch = flask.request.form.get("site_branch")
1506
if flask.request.form.get("primary_site") and had_site != 2:
1507
# Remove primary site from other repos
1508
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=2):
1509
other_repo.has_site = 1 # switch it to a regular site
1510
flask.flash(Markup(
1511
_("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(
1512
repository=other_repo.route
1513
)), category="warning")
1514
repo.has_site = 2
1515
else:
1516
repo.has_site = 1
1517
else:
1518
repo.site_branch = None
1519
repo.has_site = 0
1520
1521
db.session.commit()
1522
1523
if not (had_site, old_branch) == (repo.has_site, repo.site_branch):
1524
# Deploy the newly activated site
1525
result = celery_tasks.copy_site.delay(repo.route)
1526
1527
if had_site and not repo.has_site:
1528
# Remove the site
1529
result = celery_tasks.delete_site.delay(repo.route)
1530
1531
if repo.has_site == 2 or (had_site == 2 and had_site != repo.has_site):
1532
# Deploy all other sites which were destroyed by the primary site
1533
for other_repo in Repo.query.filter_by(owner=repo.owner, has_site=1):
1534
result = celery_tasks.copy_site.delay(other_repo.route)
1535
1536
return flask.redirect(f"/{username}/{repository}/settings", 303)
1537
1538
1539
@app.errorhandler(404)
1540
def e404(error):
1541
return flask.render_template("not-found.html"), 404
1542
1543
1544
@app.errorhandler(401)
1545
def e401(error):
1546
return flask.render_template("unauthorised.html"), 401
1547
1548
1549
@app.errorhandler(403)
1550
def e403(error):
1551
return flask.render_template("forbidden.html"), 403
1552
1553
1554
@app.errorhandler(418)
1555
def e418(error):
1556
return flask.render_template("teapot.html"), 418
1557
1558
1559
@app.errorhandler(405)
1560
def e405(error):
1561
return flask.render_template("method-not-allowed.html"), 405
1562
1563
1564
if __name__ == "__main__":
1565
app.run(debug=True, port=8080, host="0.0.0.0")
1566
1567
app.register_blueprint(repositories)
1568