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

Dismiss

 app.py

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