roundabout,
created on Thursday, 23 May 2024, 05:50:50 (1716443450),
received on Wednesday, 31 July 2024, 06:54:48 (1722408888)
Author identity: vlad <vlad.muntoiu@gmail.com>
f978bd32c4ae575b402c858e1f48515c6536698e
app.py
@@ -28,6 +28,16 @@ import config
from common import git_command from flask_babel import Babel, gettext, ngettext, force_locale import logging class No304(logging.Filter): def filter(self, record): return not record.getMessage().strip().endswith("304 -") logging.getLogger("werkzeug").addFilter(No304()) _ = gettext n_ = ngettext
@@ -50,7 +60,8 @@ app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" app.config["SESSION_COOKIE_SECURE"] = config.suggest_https # only send cookies over HTTPS if the server is configured for it app.config["SESSION_COOKIE_HTTPONLY"] = True # don't allow JS to access the cookie app.config["SESSION_COOKIE_DOMAIN"] = config.BASE_DOMAIN # don't share across subdomains, since user content is hosted thereif config.restrict_cookie_domain: app.config["SESSION_COOKIE_DOMAIN"] = config.BASE_DOMAIN # don't share across subdomains, since user content is hosted theredb = SQLAlchemy(app) bcrypt = Bcrypt(app)
@@ -468,6 +479,7 @@ def new_repo():
@app.route("/logout") def logout(): flask.session.clear() print("Logged out")flask.flash(Markup( "<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")), category="info")
@@ -795,6 +807,36 @@ def repository_commit(username, repository, sha):
file in files}, data=db.session.get(Commit, f"/{username}/{repository}/{sha}"), repo_data=repo_data, comment_query=Comment.query, ) @repositories.route("/<username>/<repository>/commit/<sha>/add_comment", methods=["POST"]) def repository_commit_add_comment(username, repository, sha): server_repo_location = os.path.join(config.REPOS_PATH, username, repository) if not os.path.exists(server_repo_location): app.logger.error(f"Cannot load {server_repo_location}") flask.abort(404) if not (get_visibility(username, repository) or get_permission_level( flask.session.get("username"), username, repository) is not None): flask.abort(403) comment = Comment( db.session.get(User, flask.session.get("username")), db.session.get(Repo, f"/{username}/{repository}"), db.session.get(Commit, f"/{username}/{repository}/{sha}"), flask.request.form["comment"], flask.request.form["file"], flask.request.form["line"], ) db.session.add(comment) db.session.commit() return flask.redirect( flask.url_for(".repository_commit", username=username, repository=repository, sha=sha), code=303)
config.py
@@ -28,6 +28,7 @@ HASHING_ROUNDS: int = 11
RESERVED_NAMES: tuple = ("git", "settings", "logout", "accounts", "info", "notifications", "about", "newrepo", "favourites",) suggest_https: bool = False # this config is intended for a test server restrict_cookie_domain: bool = False # dittoavailable_locales: list[str] = ["ro_RO", "en_GB"]
git_http.py
@@ -60,7 +60,7 @@ def get_commit_identity(identity, pusher, repo):
if relationship.permission_level == 1: return user # If no user has a higher permission level, attribute the commit to the pusher.:(# If no user has a higher permission level, attribute the commit to the pusher :(return pusher
markdown.py
@@ -9,7 +9,7 @@ def only_chars(string, chars):
return all_chars.issubset(chars) inlineRegex = r"""inline_regex = r"""(?P<imageFlag>!?) \[ (?P<urlText>[^\[\]]*) \] \((?P<urlDestination>[^\(\)]*)\) # hyperlink or media | (?P<em>\*{1,7}) (?P<textEm>(?:\\\*|[^*])*) (?P=em) # emphasis with * not requiring space on either side
@@ -261,7 +261,7 @@ def parse_line(source):
hard_break = False tokens = [] pattern = re.compile(inlineRegex, re.MULTILINE | re.DOTALL | re.VERBOSE)pattern = re.compile(inline_regex, re.MULTILINE | re.DOTALL | re.VERBOSE)matches = pattern.finditer(source) lookup = 0
models.py
@@ -11,6 +11,7 @@ __all__ = [
"Commit", "PullRequest", "EmailChangeRequest", "Comment",] import secrets
@@ -107,6 +108,7 @@ with (app.app_context()):
commits = db.relationship("Commit", back_populates="owner") posts = db.relationship("Post", back_populates="owner") comments = db.relationship("Comment", back_populates="owner")prs = db.relationship("PullRequest", back_populates="owner") notifications = db.relationship("UserNotification", back_populates="user")
@@ -163,6 +165,7 @@ with (app.app_context()):
commits = db.relationship("Commit", back_populates="repo") posts = db.relationship("Post", back_populates="repo") comments = db.relationship("Comment", back_populates="repo")repo_access = db.relationship("RepoAccess", back_populates="repo") favourites = db.relationship("RepoFavourite", back_populates="repo") heads = db.relationship("PullRequest", back_populates="head", foreign_keys="[PullRequest.head_route]")
@@ -172,7 +175,8 @@ with (app.app_context()):
server_default="0") # (the one accessible at username.localhost) site_branch = db.Column(db.String(64), nullable=True) last_post_id = db.Column(db.Integer, nullable=False, default=0)last_post_id = db.Column(db.Integer, nullable=False, default=0, server_default="0") last_comment_id = db.Column(db.Integer, nullable=False, default=0, server_default="0")def __init__(self, owner, name, visibility): self.route = f"/{owner.username}/{name}"
@@ -203,6 +207,8 @@ with (app.app_context()):
repo = db.relationship("Repo", back_populates="commits") owner = db.relationship("User", back_populates="commits") comments = db.relationship("Comment", back_populates="commit") def __init__(self, sha, owner, repo, date, message, owner_identity): self.identifier = f"{repo.route}/{sha}" self.sha = sha
@@ -279,6 +285,44 @@ with (app.app_context()):
with db.session.no_autoflush: if self.parent is not None: self.parent.update_date() class Comment(db.Model): identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True) number = db.Column(db.Integer, nullable=False) repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) commit_identifier = db.Column(db.String(227), db.ForeignKey("commit.identifier"), nullable=False) pr_id = db.Column(db.BigInteger, db.ForeignKey("pull_request.id"), nullable=True) file = db.Column(db.String(256), nullable=True) line_number = db.Column(db.Integer, nullable=True) state = db.Column(db.SmallInteger, nullable=True, default=1) review = db.Column(db.SmallInteger, nullable=True, default=0) date = db.Column(db.DateTime, default=datetime.now) message = db.Column(db.UnicodeText) repo = db.relationship("Repo", back_populates="comments") owner = db.relationship("User", back_populates="comments") commit = db.relationship("Commit", back_populates="comments") def __init__(self, owner, repo, commit, message, file, line_number, pr=None): self.identifier = f"{repo.route}/{repo.last_comment_id}" self.number = repo.last_comment_id self.repo_name = repo.route self.repo = repo self.owner_name = owner.username self.owner = owner self.commit_identifier = commit.identifier self.commit = commit self.message = message self.file = file self.line_number = line_number self.pr_id = pr repo.last_comment_id += 1class UserNotification(db.Model):
static/style.css
@@ -437,23 +437,31 @@ header {
.code-view > .line-number:first-child, .code-view > .line-number:first-child + :is(code, ins, del, x-codeline) { padding-top: 1ch; align-items: flex-end;} .code-view > .line-number:nth-last-child(2), .code-view > :is(code, ins, del, x-codeline):last-child { padding-bottom: 1ch; align-items: flex-start;} .line-number { display: inline-block; box-sizing: content-box;box-sizing: border-box;text-align: right; padding: 0 1ch;padding: 0 1ch !important;background: var(--color-code-line-number); position: sticky; left: 0; user-select: none; font-weight: 500; font: var(--mono-font); box-shadow: none; width: 100%; border-radius: 0; min-height: 0; cursor: cell;} .line-number:has(+ ins) {
@@ -577,3 +585,16 @@ strong, em {
gap: 1em; width: 100%; } .comment { /* Span all columns */ grid-column: 1 / -1; padding: 1em; background: var(--color-card); color: var(--color-card-text); font: var(--body-font); box-shadow: var(--shadow-card); z-index: 2; border-radius: var(--radius-card); border: var(--border-card); }
templates/repo.html
@@ -12,7 +12,7 @@
<header class="card-top"> <div class="navbar navbar-mini"> <ul> <li><b>{% trans %}Information{% endtrans %}</b></li><li><h4>{% trans %}Information{% endtrans %}</h4></li></ul> <x-buttonbox class="dialog-tools"> <button class="button-flat button-neutral big-button" type="submit" form="info-form"><iconify-icon icon="mdi:close"></iconify-icon></button>
templates/repository/repo-commit.html
@@ -47,16 +47,16 @@
<pre class="code-view"> {% elif vars.hunk_started %} {% if line.startswith("+") %} <span class="line-number">{{ vars.modified_line }} +</span><button class="line-number" data-file="{{ file }}" data-line="{{ vars.modified_line }}">{{ vars.modified_line }} +</button><ins>{{ line[1:] }}</ins> {% set vars.modified_line = vars.modified_line + 1 %} {% elif line.startswith("-") %} <span class="line-number">{{ vars.original_line }} -</span><button class="line-number" data-file="{{ file }}" data-line="{{ vars.original_line }}">{{ vars.original_line }} -</button><del>{{ line[1:] }}</del> {% set vars.original_line = vars.original_line + 1 %} {% elif not line.startswith("\\") %} {% if line %} <span class="line-number">{{ vars.modified_line }} </span><button class="line-number" data-file="{{ file }}" data-line="{{ vars.modified_line }}">{{ vars.modified_line }} </button><x-codeline>{{ line[1:] }}</x-codeline> {% endif %} {% if not line.startswith("@@") %}
@@ -65,6 +65,11 @@
{% endif %} {% endif %} {% endif %} {% for comment in comment_query.filter_by(commit=data, file=file, line_number=vars.original_line).all() %} <div class="comment"> {{ comment.message }} </div> {% endfor %}{% endfor %} {% if vars.hunk_started %} </pre> <!-- close the last hunk -->
@@ -76,4 +81,40 @@
</x-vbox> </x-frame> </x-vbox> <dialog id="add-comment-dialog"> <article class="card"> <header class="card-top"> <div class="navbar navbar-mini"> <ul> <li><h4>{% trans %}Add comment{% endtrans %}</h4></li> </ul> <x-buttonbox class="dialog-tools"> <button class="button-flat button-neutral big-button" type="submit" form="info-form"><iconify-icon icon="mdi:close"></iconify-icon></button> </x-buttonbox> </div> </header> <section class="card-main" style="padding-top: var(--padding-card-top);"> <form method="post" action="{{ repo_data.route }}/commit/{{ data.sha }}/add_comment"> <input type="hidden" name="file" value=""> <input type="hidden" name="line" value=""> <textarea name="comment" required></textarea> <button type="submit">Post</button> </form> </section> </article> </dialog> {% endblock %} {% block scripts %} <script> document.querySelectorAll(".line-number").forEach(function (element) { element.addEventListener("click", function () { let file = element.getAttribute("data-file"); let line = element.getAttribute("data-line"); let dialog = document.getElementById("add-comment-dialog"); dialog.querySelector("input[name=file]").value = file; dialog.querySelector("input[name=line]").value = line; dialog.showModal(); }); }); </script>{% endblock %}