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