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

Dismiss

 app.py

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