roundabout,
created on Saturday, 2 December 2023, 07:50:08 (1701503408),
received on Wednesday, 31 July 2024, 06:54:38 (1722408878)
Author identity: vlad <vlad.muntoiu@gmail.com>
2b125b919d87db572a6dd3c3ad614963112f7807
app.py
@@ -1,6 +1,7 @@
import os import random import subprocess from functools import wrapsimport cairosvg import flask
@@ -33,6 +34,23 @@ bcrypt = Bcrypt(app)
migrate = Migrate(app, db) def gitCommand(repo, data, *args): if not os.path.isdir(repo): raise FileNotFoundError("Repo not found") env = os.environ.copy() command = ["git", *args] proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) print(command) if data: proc.stdin.write(data) out, err = proc.communicate() return out def onlyChars(string, chars): for i in string: if i not in chars:
@@ -72,6 +90,7 @@ with app.app_context():
creationDate = db.Column(db.DateTime, default=datetime.utcnow) repositories = db.relationship("Repo", back_populates="owner") repoAccess = db.relationship("RepoAccess", back_populates="user")def __init__(self, username, password, email=None, displayName=None): self.username = username
@@ -87,7 +106,7 @@ with app.app_context():
avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"): cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), write_to="/tmp/gitme-avatar.png")cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), write_to="/tmp/roundabout-avatar.png")avatar = Image.open("/tmp/roundabout-avatar.png") else: avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
@@ -117,10 +136,9 @@ with app.app_context():
self.owner = owner self.visibility = visibility # Add repo access for the ownerrepoAccess = RepoAccess(user=owner, repo=self, accessLevel=2)# Add the owner as an admin repoAccess = RepoAccess(owner, self, 2)db.session.add(repoAccess) db.session.commit()class Commit(db.Model):
@@ -135,7 +153,7 @@ with app.app_context():
repo = db.relationship("Repo", back_populates="commits") def __init__(self, sha, owner, repo, date, message, ownerIdentity): self.identifier = f"/{repo.route}/{owner.username}/{sha}"self.identifier = f"/{owner.username}/{repo.name}/{sha}"self.sha = sha self.repoName = repo.route self.repo = repo
@@ -145,6 +163,28 @@ with app.app_context():
self.message = message self.ownerIdentity = ownerIdentity def getPermissionLevel(loggedIn, username, repository): user = User.query.filter_by(username=loggedIn).first() repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() if user and repo: permission = RepoAccess.query.filter_by(user=user, repo=repo).first() if permission: return permission.accessLevel return None def getVisibility(username, repository): repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() if repo: return repo.visibility return None import gitHTTP
@@ -192,13 +232,28 @@ def about():
return flask.render_template("about.html", platform=platform) @app.route("/settings/")@app.route("/settings/", methods=["GET", "POST"])def settings(): if not flask.session.get("username"):flask.abort(401)user = User.query.filter_by(username=flask.session.get("username")).first()if flask.request.method == "GET": if not flask.session.get("username"): flask.abort(401) user = User.query.filter_by(username=flask.session.get("username")).first()return flask.render_template("user-settings.html", user=user)return flask.render_template("user-settings.html", user=user) else: user = User.query.filter_by(username=flask.session.get("username")).first() user.displayName = flask.request.form["displayname"] user.URL = flask.request.form["url"] user.company = flask.request.form["company"] user.companyURL = flask.request.form["companyurl"] user.location = flask.request.form["location"] user.showMail = flask.request.form.get("showmail", user.showMail) db.session.commit() flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success") return flask.redirect(f"/{flask.session.get('username')}", code=303)@app.route("/accounts/", methods=["GET", "POST"])
@@ -357,6 +412,7 @@ def repositoryTree(username, repository, branch, subpath):
return flask.render_template("not-found.html"), 404 branches = repo.heads if os.path.isdir(os.path.join(serverRepoLocation, subpath)): files = [] blobs = []
@@ -371,6 +427,12 @@ def repositoryTree(username, repository, branch, subpath):
path = os.path.join(serverRepoLocation, file) mimetype = guessMIME(path) text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode() sha = text.split("\n")[0] identifier = f"/{username}/{repository}/{sha}" lastCommit = Commit.query.filter_by(identifier=identifier).first() info = { "name": os.path.basename(file), "serverPath": path,
@@ -378,6 +440,8 @@ def repositoryTree(username, repository, branch, subpath):
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), "size": humanSize(os.path.getsize(path)), "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", "commit": lastCommit, "shaSize": 7,} specialIcon = config.matchIcon(os.path.basename(file))
config.py
@@ -3,7 +3,7 @@ from dotenv import load_dotenv
load_dotenv("secrets.env") DB_PASSWORD = os.environ.get("DB_PASSWORD") DB_URI = f"postgresql://root:{DB_PASSWORD}@localhost/gitme"DB_URI = f"postgresql://root:{DB_PASSWORD}@localhost/roundabout"REPOS_PATH = "./repos" USERDATA_PATH = "./userdata"
@@ -14,7 +14,7 @@ AUTH_REALM = "roundabout"
AVATAR_SIZE = (192, 192) HASHING_ROUNDS = 16HASHING_ROUNDS = 11RESERVED_NAMES = ("git", "settings", "logout", "accounts", "info", "alerts", "notifications", "about",) locking = False
gitHTTP.py
@@ -1,6 +1,6 @@
import uuid from app import app, User, Repo, Commit, db, bcryptfrom app import app, gitCommand, User, Repo, Commit, RepoAccess, getPermissionLevel, getVisibility, db, bcryptimport os import shutil import config
@@ -26,26 +26,12 @@ def verifyPassword(username, password):
return False def gitCommand(repo, data, *args):if not os.path.isdir(repo):raise FileNotFoundError("Repo not found")env = os.environ.copy()command = ["git", *args]proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)print(command)if data:proc.stdin.write(data)out, err = proc.communicate()return out@app.route("/git/<username>/<repository>/git-upload-pack", methods=["POST"]) @auth.login_required def gitUploadPack(username, repository): if not (getVisibility(username, repository) or getPermissionLevel(flask.g.user, username, repository) is not None): flask.abort(403)serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git") text = gitCommand(serverRepoLocation, flask.request.data, "upload-pack", "--stateless-rpc", ".")
@@ -55,16 +41,18 @@ def gitUploadPack(username, repository):
@app.route("/git/<username>/<repository>/git-receive-pack", methods=["POST"]) @auth.login_required def gitReceivePack(username, repository): if not getPermissionLevel(flask.g.user, username, repository): flask.abort(403)serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git") text = gitCommand(serverRepoLocation, flask.request.data, "receive-pack", "--stateless-rpc", ".") sha = flask.request.data.split(b" ", 2)[1].decode() info = gitCommand(serverRepoLocation, None, "show", "-s", "--format='%H%n%at%n%cn <%ce>%n%B'").decode()info = gitCommand(serverRepoLocation, None, "show", "-s", "--format='%H%n%at%n%cn <%ce>%n%B'", sha).decode()if re.match("[0-9a-fA-F]", info[:40]):print(info.split("\n", 4))sha, time, identity, *body = info.split("\n", 4)body = "\n".join("body")if re.match("^[0-9a-fA-F]{40}$", info[:40]): print(info.split("\n", 3)) sha, time, identity, body = info.split("\n", 3)login = flask.g.user user = User.query.filter_by(username=login).first()
@@ -80,13 +68,19 @@ def gitReceivePack(username, repository):
@app.route("/git/<username>/<repository>/info/refs", methods=["GET"]) @auth.login_required def gitInfoRefs(username, repository): if not (getVisibility(username, repository) or getPermissionLevel(flask.g.user, username, repository) is not None): flask.abort(403)serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git") service = flask.request.args.get("service") if service.startswith("git"): service = service[4:] print(service)if service == "receive-pack": print(getPermissionLevel(flask.g.user, username, repository)) if not getPermissionLevel(flask.g.user, username, repository): flask.abort(403)serviceLine = f"# service=git-{service}\n" serviceLine = (f"{len(serviceLine) + 4:04x}" + serviceLine).encode()
post-receive
@@ -1,16 +0,0 @@
#!/bin/bashwhile read oldrev newrev refnamedo# Check if the refname starts with "refs/heads/"if [[ $refname == refs/heads/* ]]; then# Extract the branch name from refnamebranchname="${refname#refs/heads/}"# Change to the repository's working treecd /path/to/your/repo/working/tree# Update the working tree for the branchgit checkout -f "$branchname"fidone
templates/tree-view.html
@@ -37,8 +37,8 @@
<td style="text-align: right;">{{ file.size[0] }} {{ file.size[1] }}</td> <td> <x-hbox style="align-items: baseline; gap: 0.5ch;"> <code>DEADBEEF</code><span class="commit-message">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</span><code>{{ file.commit.sha[:file.shaSize] }}</code> <span class="commit-message">{{ file.commit.message }}</span></x-hbox> </td> </tr>
templates/user-profile.html
@@ -10,12 +10,12 @@
<x-frame style="--width: 768px;"> <x-vbox> <x-hbox style="align-items: center;"> <img src="/info/{{ user.username }}/avatar" class="avatar"><img src="/info/{{ user.username }}/avatar" class="avatar" style="width: 128px; height: 128px;"><x-vbox class="nopad" style="flex: 1 0 auto;"><x-vbox class="nopad">{% if user.displayName and user.displayName != user.username %} <hgroup id="username"> <h1 class="headline">{{ user.displayName }}</h1><h1>{{ user.displayName }}</h1><p>{{ user.username }}</p> </hgroup> {% else %}
@@ -35,31 +35,39 @@
{% if user.location %} <li><x-hbox><iconify-icon icon="mdi:map-marker-radius"></iconify-icon>{{ user.location }}</x-hbox></li> {% endif %} {% if user.showMail %} <li><a href="mailto:{{ user.email }}"><x-hbox><iconify-icon icon="mdi:email"></iconify-icon>{{ user.email }}</x-hbox></a></li> {% endif %}</ul> </x-hbox> {% if user.bio %}<x-vbox> {% if user.bio %} <article class="card" style="flex: 0 1 auto;"> <section class="card-main"> <p> {{ user.bio }} </p> </section> </article> {% endif %} <h2>Repositories</h2><article class="card" style="flex: 0 1 auto;"> <section class="card-main"> <p>{{ user.bio }}</p><ul style="list-style: none;" class="noindent"> {% for repo in repos %} <li> <article> <a href="{{ repo.route }}"><h3>{{ repo.name }}</h3></a> {% if repo.info %} <p>{{ repo.info }}</p> {% endif %} </article> </li> {% endfor %} </ul></section> </article> {% endif %}<article class="card" style="flex: 0 1 auto;"><section class="card-main"><ul style="list-style: none;" class="noindent">{% for repo in repos %}<li><article><a href="{{ repo.route }}"><h3>{{ repo.name }}</h3></a><p>{{ repo.info }}</p></article></li>{% endfor %}</ul></section></article></x-vbox></x-vbox> </x-frame> {% endblock %}
templates/user-settings.html
@@ -36,7 +36,7 @@
<x-hbox style="width: 100%; align-items: space-between;"> <x-vbox style="flex-grow: 1;" class="nopad"> <label for="company">Company or school</label> <input id="company" name="companyurl" value="{% if user.company %}{{ user.company }}{% endif %}"><input id="company" name="company" value="{% if user.company %}{{ user.company }}{% endif %}"></x-vbox> <x-vbox style="flex-grow: 1;" class="nopad"> <label for="companyurl">Link to the company's website</label>
@@ -45,11 +45,15 @@
</x-hbox> </x-vbox> <x-vbox class="nopad"> <label><input name="showmail" value="{% if user.showMail %}{{ user.showMail }}{% endif %}" type="checkbox">Show email on my profile</label><label><input name="showmail" value="showmail" {% if user.showMail %}checked{% endif %} type="checkbox">Show email on my profile</label> </x-vbox> <x-vbox class="nopad"> <label for="location">Location</label> <input id="location" name="location" value="{% if user.location %}{{ user.location }}{% endif %}" type="text"></x-vbox> <x-vbox class="nopad"> <label for="bio">Bio</label> <textarea id="bio" rows="4">{% if user.bio %}{{ user.bio }}{% endif %}</textarea><textarea id="bio" name="bio" rows="4">{% if user.bio %}{{ user.bio }}{% endif %}</textarea></x-vbox> <button type="submit">Update profile</button> </x-vbox>