roundabout,
created on Sunday, 18 February 2024, 18:07:13 (1708279633),
received on Wednesday, 31 July 2024, 06:54:41 (1722408881)
Author identity: vlad <vlad.muntoiu@gmail.com>
1b7e00a0fb514171984c96c5bc62ad9a7f623874
app.py
@@ -63,7 +63,7 @@ def default():
"user_object": user_object,
"Notification": Notification,
"unread": UserNotification.query.filter_by(user_username=username).filter(
UserNotification.attention_level > 0).count(),
UserNotification.attention_level > 0).count(),
"config": config,
"Markup": Markup,
}
@@ -128,7 +128,7 @@ def notifications():
flask.abort(401)
if flask.request.method == "GET":
return flask.render_template("notifications.html", notifications=UserNotification.query.filter_by(
user_username=flask.session.get("username")))
user_username=flask.session.get("username")))
@app.route("/accounts/", methods=["GET", "POST"])
@@ -173,7 +173,7 @@ def login():
if username in config.RESERVED_NAMES:
flask.flash(
Markup(
f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
category="error")
return flask.render_template("login.html")
@@ -181,7 +181,7 @@ def login():
if user_check:
flask.flash(
Markup(
f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
category="error")
return flask.render_template("login.html")
@@ -574,7 +574,7 @@ def repository_forum_topic(username, repository, id):
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
def repository_forum_new(username, repository):
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
repository) is not None):
repository) is not None):
flask.abort(403)
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
@@ -597,8 +597,8 @@ def repository_forum_new(username, repository):
db.session.commit()
return flask.redirect(
flask.url_for(".repository_forum_thread", username=username, repository=repository, post_id=post.number),
code=303)
flask.url_for(".repository_forum_thread", username=username, repository=repository, post_id=post.number),
code=303)
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
@@ -667,7 +667,8 @@ def repository_forum_reply(username, repository, post_id):
db.session.commit()
return flask.redirect(
flask.url_for(".repository_forum_thread", username=username, repository=repository, post_id=post_id), code=303)
flask.url_for(".repository_forum_thread", username=username, repository=repository, post_id=post_id),
code=303)
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", defaults={"score": 1})
@@ -943,28 +944,29 @@ def repository_prs(username, repository):
head = flask.request.form.get("head")
head_route = flask.request.form.get("headroute")
base = flask.request.form.get("base")
base_route = flask.request.form.get("baseroute")
if not head and base and head_route and base_route:
if not head and base and head_route:
return flask.redirect(".", 400)
head_repo = git.Repo(os.path.join(server_repo_location, head_route.lstrip("/")))
base_repo = git.Repo(os.path.join(server_repo_location, base_route.lstrip("/")))
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
base_repo = git.Repo(server_repo_location)
print(head_repo)
if head not in head_repo or base not in base_repo:
if head not in head_repo.branches or base not in base_repo.branches:
flask.flash(Markup(
"<iconify-icon icon='mdi:error'></iconify-icon>Bad branch name"),
category="error")
return flask.redirect(".", 400)
return flask.redirect(".", 303)
head_data = db.session.get(Repo, head_repo)
if head_data.visibility != 1:
head_data = db.session.get(Repo, head_route)
if not head_data.visibility:
flask.flash(Markup(
"<iconify-icon icon='mdi:error'></iconify-icon>Head can't be restricted"),
category="error")
return flask.redirect(".", 400)
return flask.redirect(".", 303)
pull_request = PullRequest(repo_data, head, head_data, base, db.session.get(User, flask.session["username"]))
pull_request = PullRequest(repo_data, head, repo_data, base, db.session.get(User, flask.session["username"]))
db.session.add(pull_request)
db.session.commit()
@@ -984,25 +986,48 @@ def repository_prs_merge(username, repository):
pull_request = db.session.get(PullRequest, id)
if pull_request:
if pull_request.base != pull_request.head:
flask.abort(400)
result = celery_tasks.merge_heads.delay(
pull_request.head_route,
pull_request.head_branch,
pull_request.base_route,
pull_request.base_branch,
simulate=True
)
task_result = worker.AsyncResult(result.id)
flask.flash(Markup(f"Merging PR in task <a href='/task/{result.id}'>{result.id}</a>"), f"task {result.id}")
db.session.delete(pull_request)
db.session.commit()
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
# db.session.delete(pull_request)
# db.session.commit()
else:
flask.abort(400)
return flask.redirect(".", 303)
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
def repository_prs_merge_stage_two(username, repository, id):
if not get_permission_level(flask.session.get("username"), username, repository):
flask.abort(401)
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
repo = git.Repo(server_repo_location)
pull_request = db.session.get(PullRequest, id)
if pull_request:
result = celery_tasks.merge_heads.delay(
pull_request.head_route,
pull_request.head_branch,
pull_request.base_route,
pull_request.base_branch,
simulate=False
)
task_result = worker.AsyncResult(result.id)
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
# db.session.delete(pull_request)
# db.session.commit()
else:
flask.abort(400)
@app.route("/task/<task_id>")
celery_tasks.py
@@ -33,21 +33,50 @@ def send_notification(notification_id, users, level):
@shared_task(ignore_result=False)
def merge_heads(head_route, head_branch, base_route, base_branch):
def merge_heads(head_route, head_branch, base_route, base_branch, simulate=True):
server_repo_location = os.path.join(config.REPOS_PATH, base_route.lstrip("/"))
if not os.path.isdir(server_repo_location):
raise FileNotFoundError(f"Repo {server_repo_location} not found, cannot merge.")
if base_route == head_route:
git_command(server_repo_location, b"", "checkout", f"{base_branch}")
out, err = git_command(server_repo_location, b"", "merge", "--no-ff", f"heads/{head_branch}", return_err=True)
if simulate:
out, err = git_command(server_repo_location, b"", "merge", "--no-commit", "--no-ff", f"heads/{head_branch}",
return_err=True)
return out, err
# Undo the merge.
git_command(server_repo_location, b"", "merge", "--abort")
else:
out, err = git_command(server_repo_location, b"", "merge", f"heads/{head_branch}",
return_err=True)
return "merge_simulator" if simulate else "merge", out, err, head_route, head_branch, base_route, base_branch
remote_url = os.path.join(config.BASE_DOMAIN, "git", base_route.lstrip("/"))
git_command(server_repo_location, b"", "remote", "add", "NEW", remote_url)
git_command(server_repo_location, b"", "remote", "update")
git_command(server_repo_location, b"", "checkout", f"{base_branch}")
git_command(server_repo_location, b"", "merge", "--allow-unrelated-histories", f"NEW/{head_branch}")
git_command(server_repo_location, b"", "remote", "rm", "NEW")
out, err = b"", b""
part_out, part_err = git_command(server_repo_location, b"", "remote", "add", "NEW", remote_url, return_err=True)
out += part_out
err += part_err
part_out, part_err = git_command(server_repo_location, b"", "remote", "update", return_err=True)
out += part_out
err += part_err
part_out, part_err = git_command(server_repo_location, b"", "checkout", f"{base_branch}", return_err=True)
out += part_out
err += part_err
if simulate:
part_out, part_err = git_command(server_repo_location, b"", "merge", "--allow-unrelated-histories",
"--no-commit", "--no-ff", f"NEW/{head_branch}", return_err=True)
else:
part_out, part_err = git_command(server_repo_location, b"", "merge", "--allow-unrelated-histories",
f"NEW/{head_branch}", return_err=True)
out += part_out
err += part_err
part_out, part_err = git_command(server_repo_location, b"", "remote", "rm", "NEW", return_err=True)
out += part_out
err += part_err
if simulate:
# Undo the merge.
git_command(server_repo_location, b"", "merge", "--abort")
return "merge_simulator" if simulate else "merge", out, err, head_route, head_branch, base_route, base_branch
jinja_utils.py
@@ -15,3 +15,8 @@ def strftime(value: datetime, syntax: str):
@app.template_filter("unixtime")
def strftime(value: datetime):
return round(value.timestamp())
@app.template_filter("decode")
def decode(value: bytes, codec: str = "UTF-8", errors: str = "strict"):
return value.decode(codec, errors)
misc_utils.py
@@ -11,7 +11,7 @@ def git_command(repo, data, *args, return_err=False):
command = ["git", *args]
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE,
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
if data:
templates/favourites.html
@@ -12,7 +12,7 @@
{% for favourite in favourites %}
<article class="card card-horizontal">
<section class="card-main flexible-space">
<h3><a href="{{ favourite.repoRoute }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></h3>
<h3><a href="{{ favourite.repo.route }}">{{ favourite.repo.owner.username }}/{{ favourite.repo.name }}</a></h3>
</section>
</article>
{% endfor %}
templates/task-monitor.html
@@ -7,7 +7,75 @@
{% endblock %}
{% block content %}
<x-frame style="--width: 896px;" class="flexible-space">
<h1>Task results</h1>
<pre>{{ result.get() }}</pre>
<x-hbox class="box-center">
<h1>Task results</h1>
<div class="flexible-space"></div>
{% if result.ready() %}
<iconify-icon icon="mdi:check" style="font-size: 2em; color: var(--color-success);"></iconify-icon>
Done
{% else %}
<iconify-icon icon="material-symbols:autorenew" style="animation: rotate 1000ms linear infinite; font-size: 2em; color: var(--color-info);"></iconify-icon>
Running...
{% endif %}
</x-hbox>
{% if result.get()[0] == "merge_simulator" %}
{% if result.get()[1] %}
<h2>Info</h2>
<pre aria-busy="true" aria-describedby="task-progress">{{ result.get()[1] | decode }}</pre>
{% endif %}
{% if result.get()[2] %}
<h2>Errors</h2>
<pre aria-busy="true" aria-describedby="task-progress">{{ result.get()[2] | decode }}</pre>
{% endif %}
{% if result.get()[1] or result.get()[2] and not result.get()[2].decode().startswith("Automatic merge went well") %}
<h2>Cannot merge your branches :/</h2>
<p>
Since we can't help you with this yet, you'll need to resolve the merge conflicts on your own
computer.
</p>
<p>
In a shell inside your repository execute:
</p>
{% if result.get()[3] != result.get()[5] %}
<pre>
<span class="decorative-dollar">git remote add UPSTREAM {{ result.get()[3] }}</span>
<span class="decorative-dollar">git remote update</span>
<span class="decorative-dollar">git checkout {{ result.get()[6] }}</span>
<span class="decorative-dollar">git merge --allow-unrelated-histories UPSTREAM/{{ result.get()[4] }}</span></pre>
<p>
Then fix your conflicts, merge, and finally, run:
</p>
<pre>
<span class="decorative-dollar">git remote rm UPSTREAM</span></pre>
<p>
and push the changes.
</p>
{% else %}
<pre>
<span class="decorative-dollar">git checkout {{ result.get()[6] }}</span>
<span class="decorative-dollar">git merge {{ result.get()[4] }}</span></pre>
<p>
Resolve your conflicts and merge, then push.
</p>
{% endif %}
{% else %}
<h2>Merge simulation went well; continue?</h2>
<a href="{{ result.get()[5] }}/prs/{{ request.args.get('pr-id') }}/merge" class="button">Merge</a>
{% endif %}
{% endif %}
</x-frame>
<style>
@keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(1turn);
}
}
.decorative-dollar::before {
content: "$ ";
}
</style>
{% endblock %}