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
)
674
else:
675
path = os.path.join(server_repo_location, subpath)
676
677
if not os.path.exists(path):
678
return flask.render_template("not-found.html"), 404
679
680
mimetype = guess_mime(path)
681
mode = mimetype.split("/", 1)[0]
682
size = human_size(os.path.getsize(path))
683
684
special_icon = config.match_icon(os.path.basename(path))
685
if special_icon:
686
icon = special_icon
687
elif os.path.isdir(path):
688
icon = config.folder_icon
689
elif mimetypes.guess_type(path)[0] in config.file_icons:
690
icon = config.file_icons[mimetypes.guess_type(path)[0]]
691
else:
692
icon = config.unknown_icon
693
694
contents = None
695
if mode == "text":
696
contents = convert_to_html(path)
697
698
return flask.render_template(
699
"repo-file.html",
700
username=username,
701
repository=repository,
702
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
703
branches=all_refs,
704
current=branch,
705
mode=mode,
706
mimetype=mimetype,
707
detailedtype=magic.from_file(path),
708
size=size,
709
icon=icon,
710
subpath=os.path.join("/", subpath),
711
extension=pathlib.Path(path).suffix,
712
basename=os.path.basename(path),
713
contents=contents,
714
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
715
is_favourite=get_favourite(flask.session.get("username"), username, repository)
716
)
717
718
719
@repositories.route("/<username>/<repository>/commit/<sha>")
720
def repository_commit(username, repository, sha):
721
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
722
if not os.path.exists(server_repo_location):
723
app.logger.error(f"Cannot load {server_repo_location}")
724
flask.abort(404)
725
if not (get_visibility(username, repository) or get_permission_level(
726
flask.session.get("username"), username,
727
repository) is not None):
728
flask.abort(403)
729
730
app.logger.info(f"Loading {server_repo_location}")
731
732
if not os.path.exists(server_repo_location):
733
app.logger.error(f"Cannot load {server_repo_location}")
734
return flask.render_template("not-found.html"), 404
735
736
repo = git.Repo(server_repo_location)
737
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
738
739
files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r",
740
"--name-only", "--no-commit-id", sha).decode().split("\n")[:-1]
741
742
print(files)
743
744
return flask.render_template(
745
"repo-commit.html",
746
username=username,
747
repository=repository,
748
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
749
is_favourite=get_favourite(flask.session.get("username"), username, repository),
750
diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff",
751
str(sha) + "^!", "--", file).decode().split("\n") for
752
file in files},
753
data=db.session.get(Commit, f"/{username}/{repository}/{sha}"),
754
)
755
756
757
@repositories.route("/<username>/<repository>/forum/")
758
def repository_forum(username, repository):
759
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
760
if not os.path.exists(server_repo_location):
761
app.logger.error(f"Cannot load {server_repo_location}")
762
flask.abort(404)
763
if not (get_visibility(username, repository) or get_permission_level(
764
flask.session.get("username"), username,
765
repository) is not None):
766
flask.abort(403)
767
768
app.logger.info(f"Loading {server_repo_location}")
769
770
if not os.path.exists(server_repo_location):
771
app.logger.error(f"Cannot load {server_repo_location}")
772
return flask.render_template("not-found.html"), 404
773
774
repo = git.Repo(server_repo_location)
775
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
776
user = User.query.filter_by(username=flask.session.get("username")).first()
777
relationships = RepoAccess.query.filter_by(repo=repo_data)
778
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
779
780
return flask.render_template(
781
"repo-forum.html",
782
username=username,
783
repository=repository,
784
repo_data=repo_data,
785
relationships=relationships,
786
repo=repo,
787
user_relationship=user_relationship,
788
Post=Post,
789
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
790
is_favourite=get_favourite(flask.session.get("username"), username, repository),
791
default_branch=repo_data.default_branch
792
)
793
794
795
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
796
def repository_forum_topic(username, repository, id):
797
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
798
if not os.path.exists(server_repo_location):
799
app.logger.error(f"Cannot load {server_repo_location}")
800
flask.abort(404)
801
if not (get_visibility(username, repository) or get_permission_level(
802
flask.session.get("username"), username,
803
repository) is not None):
804
flask.abort(403)
805
806
app.logger.info(f"Loading {server_repo_location}")
807
808
if not os.path.exists(server_repo_location):
809
app.logger.error(f"Cannot load {server_repo_location}")
810
return flask.render_template("not-found.html"), 404
811
812
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
813
user = User.query.filter_by(username=flask.session.get("username")).first()
814
relationships = RepoAccess.query.filter_by(repo=repo_data)
815
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
816
817
post = Post.query.filter_by(id=id).first()
818
819
return flask.render_template(
820
"repo-topic.html",
821
username=username,
822
repository=repository,
823
repo_data=repo_data,
824
relationships=relationships,
825
user_relationship=user_relationship,
826
post=post,
827
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
828
is_favourite=get_favourite(flask.session.get("username"), username, repository),
829
default_branch=repo_data.default_branch
830
)
831
832
833
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
834
def repository_forum_new(username, repository):
835
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
836
if not os.path.exists(server_repo_location):
837
app.logger.error(f"Cannot load {server_repo_location}")
838
flask.abort(404)
839
if not (get_visibility(username, repository) or get_permission_level(
840
flask.session.get("username"), username,
841
repository) is not None):
842
flask.abort(403)
843
844
app.logger.info(f"Loading {server_repo_location}")
845
846
if not os.path.exists(server_repo_location):
847
app.logger.error(f"Cannot load {server_repo_location}")
848
return flask.render_template("not-found.html"), 404
849
850
repo = git.Repo(server_repo_location)
851
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
852
user = User.query.filter_by(username=flask.session.get("username")).first()
853
relationships = RepoAccess.query.filter_by(repo=repo_data)
854
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
855
856
post = Post(user, repo_data, None, flask.request.form["subject"],
857
flask.request.form["message"])
858
859
db.session.add(post)
860
db.session.commit()
861
862
return flask.redirect(
863
flask.url_for(".repository_forum_thread", username=username, repository=repository,
864
post_id=post.number),
865
code=303)
866
867
868
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
869
def repository_forum_thread(username, repository, post_id):
870
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
871
if not os.path.exists(server_repo_location):
872
app.logger.error(f"Cannot load {server_repo_location}")
873
flask.abort(404)
874
if not (get_visibility(username, repository) or get_permission_level(
875
flask.session.get("username"), username,
876
repository) is not None):
877
flask.abort(403)
878
879
app.logger.info(f"Loading {server_repo_location}")
880
881
if not os.path.exists(server_repo_location):
882
app.logger.error(f"Cannot load {server_repo_location}")
883
return flask.render_template("not-found.html"), 404
884
885
repo = git.Repo(server_repo_location)
886
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
887
user = User.query.filter_by(username=flask.session.get("username")).first()
888
relationships = RepoAccess.query.filter_by(repo=repo_data)
889
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
890
891
if user:
892
max_post_nesting = user.max_post_nesting
893
else:
894
max_post_nesting = 2
895
896
return flask.render_template(
897
"repo-forum-thread.html",
898
username=username,
899
repository=repository,
900
repo_data=repo_data,
901
relationships=relationships,
902
repo=repo,
903
Post=Post,
904
user_relationship=user_relationship,
905
post_id=post_id,
906
max_post_nesting=max_post_nesting,
907
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
908
is_favourite=get_favourite(flask.session.get("username"), username, repository),
909
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
910
has_permission=not ((not get_permission_level(flask.session.get("username"), username,
911
repository)) and db.session.get(Post,
912
f"/{username}/{repository}/{post_id}").owner.username != flask.session.get("username")),
913
)
914
915
916
@repositories.route("/<username>/<repository>/forum/<int:post_id>/change-state",
917
methods=["POST"])
918
def repository_forum_change_state(username, repository, post_id):
919
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
920
if not os.path.exists(server_repo_location):
921
app.logger.error(f"Cannot load {server_repo_location}")
922
flask.abort(404)
923
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"):
924
flask.abort(403)
925
926
app.logger.info(f"Loading {server_repo_location}")
927
928
repo = git.Repo(server_repo_location)
929
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
930
user = User.query.filter_by(username=flask.session.get("username")).first()
931
relationships = RepoAccess.query.filter_by(repo=repo_data)
932
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
933
934
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
935
936
if not post:
937
flask.abort(404)
938
939
post.state = int(flask.request.form["new-state"])
940
941
db.session.commit()
942
943
return flask.redirect(
944
flask.url_for(".repository_forum_thread", username=username, repository=repository,
945
post_id=post_id),
946
code=303)
947
948
949
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
950
def repository_forum_reply(username, repository, post_id):
951
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
952
if not os.path.exists(server_repo_location):
953
app.logger.error(f"Cannot load {server_repo_location}")
954
flask.abort(404)
955
if not (get_visibility(username, repository) or get_permission_level(
956
flask.session.get("username"), username,
957
repository) is not None):
958
flask.abort(403)
959
960
app.logger.info(f"Loading {server_repo_location}")
961
962
if not os.path.exists(server_repo_location):
963
app.logger.error(f"Cannot load {server_repo_location}")
964
return flask.render_template("not-found.html"), 404
965
966
repo = git.Repo(server_repo_location)
967
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
968
user = User.query.filter_by(username=flask.session.get("username")).first()
969
relationships = RepoAccess.query.filter_by(repo=repo_data)
970
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
971
if not user:
972
flask.abort(401)
973
974
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
975
post = Post(user, repo_data, parent, flask.request.form["subject"],
976
flask.request.form["message"])
977
978
db.session.add(post)
979
post.update_date()
980
db.session.commit()
981
982
return flask.redirect(
983
flask.url_for(".repository_forum_thread", username=username, repository=repository,
984
post_id=post_id),
985
code=303)
986
987
988
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
989
defaults={"score": 1})
990
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
991
defaults={"score": -1})
992
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
993
def repository_forum_vote(username, repository, post_id, score):
994
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
995
if not os.path.exists(server_repo_location):
996
app.logger.error(f"Cannot load {server_repo_location}")
997
flask.abort(404)
998
if not (get_visibility(username, repository) or get_permission_level(
999
flask.session.get("username"), username,
1000
repository) is not None):
1001
flask.abort(403)
1002
1003
app.logger.info(f"Loading {server_repo_location}")
1004
1005
if not os.path.exists(server_repo_location):
1006
app.logger.error(f"Cannot load {server_repo_location}")
1007
return flask.render_template("not-found.html"), 404
1008
1009
repo = git.Repo(server_repo_location)
1010
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1011
user = User.query.filter_by(username=flask.session.get("username")).first()
1012
relationships = RepoAccess.query.filter_by(repo=repo_data)
1013
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1014
if not user:
1015
flask.abort(401)
1016
1017
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
1018
1019
if score:
1020
old_relationship = PostVote.query.filter_by(user_username=user.username,
1021
post_identifier=post.identifier).first()
1022
if old_relationship:
1023
if score == old_relationship.vote_score:
1024
db.session.delete(old_relationship)
1025
post.vote_sum -= old_relationship.vote_score
1026
else:
1027
post.vote_sum -= old_relationship.vote_score
1028
post.vote_sum += score
1029
old_relationship.vote_score = score
1030
else:
1031
relationship = PostVote(user, post, score)
1032
post.vote_sum += score
1033
db.session.add(relationship)
1034
1035
db.session.commit()
1036
1037
user_vote = PostVote.query.filter_by(user_username=user.username,
1038
post_identifier=post.identifier).first()
1039
response = flask.make_response(
1040
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
1041
response.content_type = "text/plain"
1042
1043
return response
1044
1045
1046
@repositories.route("/<username>/<repository>/favourite")
1047
def repository_favourite(username, repository):
1048
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1049
if not os.path.exists(server_repo_location):
1050
app.logger.error(f"Cannot load {server_repo_location}")
1051
flask.abort(404)
1052
if not (get_visibility(username, repository) or get_permission_level(
1053
flask.session.get("username"), username,
1054
repository) is not None):
1055
flask.abort(403)
1056
1057
app.logger.info(f"Loading {server_repo_location}")
1058
1059
if not os.path.exists(server_repo_location):
1060
app.logger.error(f"Cannot load {server_repo_location}")
1061
return flask.render_template("not-found.html"), 404
1062
1063
repo = git.Repo(server_repo_location)
1064
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1065
user = User.query.filter_by(username=flask.session.get("username")).first()
1066
relationships = RepoAccess.query.filter_by(repo=repo_data)
1067
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1068
if not user:
1069
flask.abort(401)
1070
1071
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
1072
repo_route=repo_data.route).first()
1073
if old_relationship:
1074
db.session.delete(old_relationship)
1075
else:
1076
relationship = RepoFavourite(user, repo_data)
1077
db.session.add(relationship)
1078
1079
db.session.commit()
1080
1081
return flask.redirect(flask.url_for("favourites"), code=303)
1082
1083
1084
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
1085
def repository_users(username, repository):
1086
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1087
if not os.path.exists(server_repo_location):
1088
app.logger.error(f"Cannot load {server_repo_location}")
1089
flask.abort(404)
1090
if not (get_visibility(username, repository) or get_permission_level(
1091
flask.session.get("username"), username,
1092
repository) is not None):
1093
flask.abort(403)
1094
1095
app.logger.info(f"Loading {server_repo_location}")
1096
1097
if not os.path.exists(server_repo_location):
1098
app.logger.error(f"Cannot load {server_repo_location}")
1099
return flask.render_template("not-found.html"), 404
1100
1101
repo = git.Repo(server_repo_location)
1102
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1103
user = User.query.filter_by(username=flask.session.get("username")).first()
1104
relationships = RepoAccess.query.filter_by(repo=repo_data)
1105
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
1106
1107
if flask.request.method == "GET":
1108
return flask.render_template(
1109
"repo-users.html",
1110
username=username,
1111
repository=repository,
1112
repo_data=repo_data,
1113
relationships=relationships,
1114
repo=repo,
1115
user_relationship=user_relationship,
1116
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1117
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1118
)
1119
else:
1120
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1121
flask.abort(401)
1122
1123
if flask.request.form.get("new-username"):
1124
# Create new relationship
1125
new_user = User.query.filter_by(
1126
username=flask.request.form.get("new-username")).first()
1127
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
1128
db.session.add(relationship)
1129
db.session.commit()
1130
if flask.request.form.get("update-username"):
1131
# Create new relationship
1132
updated_user = User.query.filter_by(
1133
username=flask.request.form.get("update-username")).first()
1134
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
1135
if flask.request.form.get("update-level") == -1:
1136
relationship.delete()
1137
else:
1138
relationship.access_level = flask.request.form.get("update-level")
1139
db.session.commit()
1140
1141
return flask.redirect(
1142
app.url_for(".repository_users", username=username, repository=repository))
1143
1144
1145
@repositories.route("/<username>/<repository>/branches/")
1146
def repository_branches(username, repository):
1147
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1148
if not os.path.exists(server_repo_location):
1149
app.logger.error(f"Cannot load {server_repo_location}")
1150
flask.abort(404)
1151
if not (get_visibility(username, repository) or get_permission_level(
1152
flask.session.get("username"), username,
1153
repository) is not None):
1154
flask.abort(403)
1155
1156
app.logger.info(f"Loading {server_repo_location}")
1157
1158
if not os.path.exists(server_repo_location):
1159
app.logger.error(f"Cannot load {server_repo_location}")
1160
return flask.render_template("not-found.html"), 404
1161
1162
repo = git.Repo(server_repo_location)
1163
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1164
1165
return flask.render_template(
1166
"repo-branches.html",
1167
username=username,
1168
repository=repository,
1169
repo_data=repo_data,
1170
repo=repo,
1171
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1172
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1173
)
1174
1175
1176
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1177
@repositories.route("/<username>/<repository>/log/<branch>/")
1178
def repository_log(username, repository, branch):
1179
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1180
if not os.path.exists(server_repo_location):
1181
app.logger.error(f"Cannot load {server_repo_location}")
1182
flask.abort(404)
1183
if not (get_visibility(username, repository) or get_permission_level(
1184
flask.session.get("username"), username,
1185
repository) is not None):
1186
flask.abort(403)
1187
1188
app.logger.info(f"Loading {server_repo_location}")
1189
1190
if not os.path.exists(server_repo_location):
1191
app.logger.error(f"Cannot load {server_repo_location}")
1192
return flask.render_template("not-found.html"), 404
1193
1194
repo = git.Repo(server_repo_location)
1195
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1196
if not repo_data.default_branch:
1197
if repo.heads:
1198
repo_data.default_branch = repo.heads[0].name
1199
else:
1200
return flask.render_template("empty.html",
1201
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1202
if not branch:
1203
branch = repo_data.default_branch
1204
return flask.redirect(f"./{branch}", code=302)
1205
1206
if branch.startswith("tag:"):
1207
ref = f"tags/{branch[4:]}"
1208
elif branch.startswith("~"):
1209
ref = branch[1:]
1210
else:
1211
ref = f"heads/{branch}"
1212
1213
ref = ref.replace("~", "/") # encode slashes for URL support
1214
1215
try:
1216
repo.git.checkout("-f", ref)
1217
except git.exc.GitCommandError:
1218
return flask.render_template("not-found.html"), 404
1219
1220
branches = repo.heads
1221
1222
all_refs = []
1223
for ref in repo.heads:
1224
all_refs.append((ref, "head"))
1225
for ref in repo.tags:
1226
all_refs.append((ref, "tag"))
1227
1228
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1229
git_command(server_repo_location, None, "log",
1230
"--format='%H'").decode().split("\n")]
1231
1232
commits = Commit.query.filter(Commit.identifier.in_(commit_list)).order_by(Commit.author_date.desc())
1233
page_number = flask.request.args.get("page", 1, type=int)
1234
if flask.session.get("username"):
1235
default_page_length = db.session.get(User, flask.session.get("username")).default_page_length
1236
else:
1237
default_page_length = 16
1238
page_length = flask.request.args.get("per_page", default_page_length, type=int)
1239
page_listing = db.paginate(commits, page=page_number, per_page=page_length)
1240
1241
if page_listing.has_next:
1242
next_page = page_listing.next_num
1243
else:
1244
next_page = None
1245
1246
if page_listing.has_prev:
1247
prev_page = page_listing.prev_num
1248
else:
1249
prev_page = None
1250
1251
return flask.render_template(
1252
"repo-log.html",
1253
username=username,
1254
repository=repository,
1255
branches=all_refs,
1256
current=branch,
1257
repo_data=repo_data,
1258
repo=repo,
1259
commits=page_listing,
1260
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1261
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1262
page_number=page_number,
1263
page_length=page_length,
1264
next_page=next_page,
1265
prev_page=prev_page,
1266
num_pages=page_listing.pages
1267
)
1268
1269
1270
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1271
def repository_prs(username, repository):
1272
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1273
if not os.path.exists(server_repo_location):
1274
app.logger.error(f"Cannot load {server_repo_location}")
1275
flask.abort(404)
1276
if not (get_visibility(username, repository) or get_permission_level(
1277
flask.session.get("username"), username,
1278
repository) is not None):
1279
flask.abort(403)
1280
1281
app.logger.info(f"Loading {server_repo_location}")
1282
1283
if not os.path.exists(server_repo_location):
1284
app.logger.error(f"Cannot load {server_repo_location}")
1285
return flask.render_template("not-found.html"), 404
1286
1287
if flask.request.method == "GET":
1288
repo = git.Repo(server_repo_location)
1289
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1290
user = User.query.filter_by(username=flask.session.get("username")).first()
1291
1292
return flask.render_template(
1293
"repo-prs.html",
1294
username=username,
1295
repository=repository,
1296
repo_data=repo_data,
1297
repo=repo,
1298
PullRequest=PullRequest,
1299
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1300
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1301
default_branch=repo_data.default_branch,
1302
branches=repo.branches
1303
)
1304
1305
else:
1306
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1307
head = flask.request.form.get("head")
1308
head_route = flask.request.form.get("headroute")
1309
base = flask.request.form.get("base")
1310
1311
if not head and base and head_route:
1312
return flask.redirect(".", 400)
1313
1314
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1315
base_repo = git.Repo(server_repo_location)
1316
print(head_repo)
1317
1318
if head not in head_repo.branches or base not in base_repo.branches:
1319
flask.flash(Markup(
1320
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1321
category="error")
1322
return flask.redirect(".", 303)
1323
1324
head_data = db.session.get(Repo, head_route)
1325
if not head_data.visibility:
1326
flask.flash(Markup(
1327
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1328
"Head can't be restricted")),
1329
category="error")
1330
return flask.redirect(".", 303)
1331
1332
pull_request = PullRequest(repo_data, head, head_data, base,
1333
db.session.get(User, flask.session["username"]))
1334
1335
db.session.add(pull_request)
1336
db.session.commit()
1337
1338
return flask.redirect(".", 303)
1339
1340
1341
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1342
def repository_prs_merge(username, repository):
1343
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1344
if not os.path.exists(server_repo_location):
1345
app.logger.error(f"Cannot load {server_repo_location}")
1346
flask.abort(404)
1347
if not (get_visibility(username, repository) or get_permission_level(
1348
flask.session.get("username"), username,
1349
repository) is not None):
1350
flask.abort(403)
1351
1352
if not get_permission_level(flask.session.get("username"), username, repository):
1353
flask.abort(401)
1354
1355
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1356
repo = git.Repo(server_repo_location)
1357
id = flask.request.form.get("id")
1358
1359
pull_request = db.session.get(PullRequest, id)
1360
1361
if pull_request:
1362
result = celery_tasks.merge_heads.delay(
1363
pull_request.head_route,
1364
pull_request.head_branch,
1365
pull_request.base_route,
1366
pull_request.base_branch,
1367
simulate=True
1368
)
1369
task_result = worker.AsyncResult(result.id)
1370
1371
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1372
# db.session.delete(pull_request)
1373
# db.session.commit()
1374
else:
1375
flask.abort(400)
1376
1377
1378
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1379
def repository_prs_merge_stage_two(username, repository, id):
1380
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1381
if not os.path.exists(server_repo_location):
1382
app.logger.error(f"Cannot load {server_repo_location}")
1383
flask.abort(404)
1384
if not (get_visibility(username, repository) or get_permission_level(
1385
flask.session.get("username"), username,
1386
repository) is not None):
1387
flask.abort(403)
1388
1389
if not get_permission_level(flask.session.get("username"), username, repository):
1390
flask.abort(401)
1391
1392
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1393
repo = git.Repo(server_repo_location)
1394
1395
pull_request = db.session.get(PullRequest, id)
1396
1397
if pull_request:
1398
result = celery_tasks.merge_heads.delay(
1399
pull_request.head_route,
1400
pull_request.head_branch,
1401
pull_request.base_route,
1402
pull_request.base_branch,
1403
simulate=False
1404
)
1405
task_result = worker.AsyncResult(result.id)
1406
1407
pull_request.state = 1
1408
db.session.commit()
1409
1410
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1411
# db.session.delete(pull_request)
1412
else:
1413
flask.abort(400)
1414
1415
1416
@app.route("/task/<task_id>")
1417
def task_monitor(task_id):
1418
task_result = worker.AsyncResult(task_id)
1419
print(task_result.status)
1420
1421
return flask.render_template("task-monitor.html", result=task_result)
1422
1423
1424
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1425
def repository_prs_delete(username, repository):
1426
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1427
if not os.path.exists(server_repo_location):
1428
app.logger.error(f"Cannot load {server_repo_location}")
1429
flask.abort(404)
1430
if not (get_visibility(username, repository) or get_permission_level(
1431
flask.session.get("username"), username,
1432
repository) is not None):
1433
flask.abort(403)
1434
1435
if not get_permission_level(flask.session.get("username"), username, repository):
1436
flask.abort(401)
1437
1438
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1439
repo = git.Repo(server_repo_location)
1440
id = flask.request.form.get("id")
1441
1442
pull_request = db.session.get(PullRequest, id)
1443
1444
if pull_request:
1445
pull_request.state = 2
1446
db.session.commit()
1447
1448
return flask.redirect(".", 303)
1449
1450
1451
@repositories.route("/<username>/<repository>/settings/")
1452
def repository_settings(username, repository):
1453
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1454
flask.abort(401)
1455
1456
return flask.render_template("repo-settings.html", username=username, repository=repository)
1457
1458
1459
@app.errorhandler(404)
1460
def e404(error):
1461
return flask.render_template("not-found.html"), 404
1462
1463
1464
@app.errorhandler(401)
1465
def e401(error):
1466
return flask.render_template("unauthorised.html"), 401
1467
1468
1469
@app.errorhandler(403)
1470
def e403(error):
1471
return flask.render_template("forbidden.html"), 403
1472
1473
1474
@app.errorhandler(418)
1475
def e418(error):
1476
return flask.render_template("teapot.html"), 418
1477
1478
1479
@app.errorhandler(405)
1480
def e405(error):
1481
return flask.render_template("method-not-allowed.html"), 405
1482
1483
1484
if __name__ == "__main__":
1485
app.run(debug=True, port=8080, host="0.0.0.0")
1486
1487
app.register_blueprint(repositories)
1488