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