celery_tasks.py
Python script, ASCII text executable
1import common 2import time 3import os 4import config 5import email_send 6import shutil 7from celery import shared_task 8from app import db, _ 9from smtplib import SMTP 10from celery.utils.log import get_task_logger 11from sqlalchemy.orm import make_transient 12from datetime import datetime 13 14 15@shared_task(ignore_result=False) 16def send_notification(user_notification_id): 17from models import UserNotification 18user_notification = db.session.get(UserNotification, user_notification_id) 19user = user_notification.user 20notification = user_notification.notification 21 22if user.email: 23with (SMTP(config.MAIL_SERVER) as mail): 24if notification.data.get("type") == "welcome": 25message = email_send.make_multipart_message( 26"Welcome, {username}".format(username=user.username), 27config.NOTIFICATION_EMAIL, 28user.email, 29"welcome", 30username=user.username) 31 32mail.sendmail(config.NOTIFICATION_EMAIL, user.email, message) 33 34return 0 # notification sent successfully 35 36 37@shared_task(ignore_result=False) 38def merge_heads(head_route, head_branch, base_route, base_branch, simulate=True): 39from models import Repo, Commit 40server_repo_location = os.path.join(config.REPOS_PATH, base_route.lstrip("/")) 41if not os.path.isdir(server_repo_location): 42raise FileNotFoundError(f"Repo {server_repo_location} not found, cannot merge.") 43 44if base_route == head_route: 45common.git_command(server_repo_location, b"", "checkout", f"{base_branch}") 46if simulate: 47out, err, merge_exit = common.git_command(server_repo_location, b"", "merge", "--no-commit", "--no-ff", f"heads/{head_branch}", 48return_err=True, return_exit=True) 49 50# Undo the merge. 51common.git_command(server_repo_location, b"", "merge", "--abort") 52else: 53out, err, merge_exit = common.git_command(server_repo_location, b"", "merge", f"heads/{head_branch}", 54return_err=True, return_exit=True) 55 56new_commits = common.git_command(server_repo_location, b"", "log", "--oneline", f"heads/{base_branch}..heads/{head_branch}") 57 58return "merge_simulator" if simulate else "merge", out, err, head_route, head_branch, base_route, base_branch, merge_exit, new_commits 59 60# Otherwise, we need to fetch the head repo. 61remote_url = "https://" if config.suggest_https else "http://" + os.path.join(config.BASE_DOMAIN + f":{config.port}" if config.port not in {80, 443} else "", "git", head_route.lstrip("/")) 62 63out, err = b"", b"" 64part_out, part_err = common.git_command(server_repo_location, b"", "remote", "add", "NEW", remote_url, return_err=True) 65out += part_out 66err += part_err 67part_out, part_err = common.git_command(server_repo_location, b"", "remote", "update", return_err=True) 68out += part_out 69err += part_err 70part_out, part_err = common.git_command(server_repo_location, b"", "fetch", "NEW", f"{head_branch}", return_err=True) 71out += part_out 72err += part_err 73part_out, part_err = common.git_command(server_repo_location, b"", "checkout", f"{base_branch}", return_err=True) 74out += part_out 75err += part_err 76new_commits, part_err = common.git_command(server_repo_location, b"", "log", "--pretty=format:\"%H\"", f"heads/{base_branch}..NEW/{head_branch}", "--", return_err=True) 77new_commits = new_commits.decode().splitlines() 78err += part_err 79 80if simulate: 81part_out, part_err, merge_exit = common.git_command(server_repo_location, b"", "merge", "--allow-unrelated-histories", 82"--no-commit", "--no-ff", f"NEW/{head_branch}", return_err=True, return_exit=True) 83else: 84part_out, part_err, merge_exit = common.git_command(server_repo_location, b"", "merge", "--allow-unrelated-histories", 85f"NEW/{head_branch}", return_err=True, return_exit=True) 86 87diff, diff_exit = common.git_command(server_repo_location, b"", "diff", "--check", return_exit=True) 88 89out += part_out 90err += part_err 91part_out, part_err = common.git_command(server_repo_location, b"", "remote", "rm", "NEW", return_err=True) 92out += part_out 93err += part_err 94if simulate: 95# Undo the merge. 96common.git_command(server_repo_location, b"", "merge", "--abort") 97else: 98# Copy the commits rows from the head repo to the base repo 99for commit in new_commits: 100commit_data = Commit.query.filter_by(repo_name=head_route, sha=commit).first() 101 102db.session.expunge(commit_data) 103make_transient(commit_data) 104 105commit_data.repo_name = base_route 106commit_data.identifier = f"{base_route}/{commit_data.sha}" 107commit_data.receive_date = datetime.now() 108db.session.add(commit_data) 109 110db.session.commit() 111 112return "merge_simulator" if simulate else "merge", out, err, head_route, head_branch, base_route, base_branch, merge_exit, new_commits 113 114 115@shared_task(ignore_result=False) 116def copy_site(route): 117from models import Repo 118repo = db.session.get(Repo, route) 119server_repo_location = os.path.join(config.REPOS_PATH, route.lstrip("/")) 120subdomain = repo.owner.username 121subpath = repo.name if repo.has_site != 2 else "" 122site_location = os.path.join(config.SITE_PATH, subdomain, subpath) 123# Get the branch to be used for the site; if it somehow doesn't exist, use the default branch. 124branch = repo.site_branch or repo.default_branch 125# Make a shallow clone of the repo; this prevents getting the full git database when it's not needed. 126if os.path.isdir(site_location): 127# Delete the old site. 128shutil.rmtree(site_location) 129 130common.git_command(config.SITE_PATH, b"", "clone", "--depth=1", f"--branch={branch}", os.path.join(os.getcwd(), server_repo_location), os.path.join(subdomain, subpath)) 131 132 133@shared_task(ignore_result=False) 134def delete_site(route): 135from models import Repo 136repo = db.session.get(Repo, route) 137subdomain = repo.owner.username 138subpath = repo.name if repo.has_site != 2 else "." 139site_location = os.path.join(config.SITE_PATH, subdomain, subpath) 140if os.path.isdir(site_location): 141shutil.rmtree(site_location) 142 143# Redo the primary site. 144primary_site = Repo.query.filter_by(owner=repo.owner, has_site=2).first() 145if primary_site: 146copy_site(primary_site.route) 147 148 149@shared_task(ignore_result=False) 150def request_email_change(username, email): 151from models import User, EmailChangeRequest 152user = db.session.get(User, username) 153 154request = EmailChangeRequest(user, email) 155 156db.session.add(request) 157db.session.commit() 158 159message = email_send.make_multipart_message( 160"Email change request for {username}".format(username=username), 161config.NOTIFICATION_EMAIL, 162email, 163"email-change", 164username=username, 165code=request.code, 166new_email=email, 167url="https://" if config.suggest_https else "http://" + config.BASE_DOMAIN + "/settings/confirm-email/" + request.code 168) 169