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