Add support for New Tag update detection trigger

This commit is contained in:
rubenwardy 2021-01-29 22:20:44 +00:00
parent 665bfd64d2
commit 09f8302e74
8 changed files with 155 additions and 61 deletions

@ -24,7 +24,7 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import *
from app.rediscache import has_key, set_key, make_download_key
from app.tasks.importtasks import makeVCSRelease, checkZipRelease
from app.tasks.importtasks import makeVCSRelease, checkZipRelease, check_update_config
from app.utils import *
from . import bp
@ -278,6 +278,7 @@ def update_config(package):
flash("Deleted update configuration", "success")
if package.update_config:
db.session.delete(package.update_config)
db.session.commit()
else:
if package.update_config is None:
package.update_config = PackageUpdateConfig()
@ -287,7 +288,15 @@ def update_config(package):
package.update_config.ref = nonEmptyOrNone(form.ref.data)
package.update_config.make_release = form.action.data == "make_release"
db.session.commit()
if package.update_config.last_commit is None:
last_release = package.releases.first()
if last_release and last_release.commit_hash:
package.update_config.last_commit = last_release.commit_hash
db.session.commit()
if package.update_config.last_commit is None:
check_update_config.delay(package.id)
if not form.disable.data and package.releases.count() == 0:
flash("Now, please create an initial release", "success")

@ -75,7 +75,7 @@ def view_editor():
outdated_packages = Package.query \
.filter(Package.state == PackageState.APPROVED,
Package.update_config.has(outdated=True)).count()
Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None))).count()
return render_template("todo/editor.html", current_tab="editor",
packages=packages, wip_packages=wip_packages, releases=releases, screenshots=screenshots,
@ -165,7 +165,7 @@ def view_user(username=None):
outdated_packages = user.maintained_packages \
.filter(Package.state != PackageState.DELETED,
Package.update_config.has(outdated=True)) \
Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None))) \
.order_by(db.asc(Package.title)).all()
topics_to_add = ForumTopic.query \
@ -184,6 +184,7 @@ def view_user(username=None):
def outdated():
outdated_packages = Package.query \
.filter(Package.state == PackageState.APPROVED,
Package.update_config.has(outdated=True)).all()
Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None)))\
.order_by(db.desc(PackageUpdateConfig.outdated_at)).all()
return render_template("todo/outdated.html", current_tab="outdated", outdated_packages=outdated_packages)

@ -861,7 +861,7 @@ class PackageRelease(db.Model):
self.approved = True
if self.package.update_config:
self.package.update_config.outdated = False
self.package.update_config.outdated_at = None
self.package.update_config.last_commit = self.commit_hash
return True
@ -955,11 +955,15 @@ class PackageUpdateConfig(db.Model):
package = db.relationship("Package", back_populates="update_config", foreign_keys=[package_id])
last_commit = db.Column(db.String(41), nullable=True, default=None)
last_tag = db.Column(db.String(41), nullable=True, default=None)
# Set to true when an outdated notification is sent. Set to false when a release is created
outdated = db.Column(db.Boolean, nullable=False, default=False)
# Set to now when an outdated notification is sent. Set to None when a release is created
outdated_at = db.Column(db.DateTime, nullable=True, default=None)
trigger = db.Column(db.Enum(PackageUpdateTrigger), nullable=False, default=PackageUpdateTrigger.COMMIT)
ref = db.Column(db.String(41), nullable=True, default=None)
make_release = db.Column(db.Boolean, nullable=False, default=False)
def set_outdated(self):
self.outdated_at = datetime.datetime.utcnow()

@ -106,6 +106,24 @@ def get_commit_hash(git_url, ref_name=None):
return remote_refs.get(ref_name)
def get_latest_tag(git_url):
with get_temp_dir() as git_dir:
repo = git.Repo.init(git_dir)
origin = repo.create_remote("origin", url=git_url)
origin.fetch()
refs = repo.git.ls_remote(tags=True, sort="creatordate").split('\n')
if len(refs) == 0:
return None
last_ref = refs[-1]
hash_ref_list = last_ref.split('\t')
tag = hash_ref_list[1].replace("refs/tags/", "")
commit_hash = repo.git.rev_parse(tag + "^{}")
return tag, commit_hash
@celery.task()
def getMeta(urlstr, author):
with clone_repo(urlstr, recursive=True) as repo:
@ -297,6 +315,67 @@ def importForeignDownloads(self, id):
db.session.commit()
def check_update_config_impl(package):
config = package.update_config
if config.trigger == PackageUpdateTrigger.COMMIT:
tag = None
commit = get_commit_hash(package.repo, package.update_config.ref)
elif config.trigger == PackageUpdateTrigger.TAG:
tag, commit = get_latest_tag(package.repo)
else:
raise TaskError("Unknown update trigger")
if config.last_commit == commit:
if tag and config.last_tag != tag:
config.last_tag = tag
db.session.commit()
return
if not config.last_commit:
config.last_commit = commit
config.last_tag = tag
db.session.commit()
return
if config.make_release:
rel = PackageRelease()
rel.package = package
rel.title = tag if tag else commit[0:5]
rel.url = ""
rel.task_id = uuid()
db.session.add(rel)
db.session.commit()
makeVCSRelease.apply_async((rel.id, commit), task_id=rel.task_id)
elif config.outdated_at is None:
config.set_outdated()
if config.trigger == PackageUpdateTrigger.COMMIT:
msg_last = ""
if config.last_commit:
msg_last = " The last commit was {}".format(config.last_commit[0:5])
msg = "New commit {} found on the Git repo, is the package outdated?{}" \
.format(commit[0:5], msg_last)
else:
msg_last = ""
if config.last_tag:
msg_last = " The last tag was {}".format(config.last_tag)
msg = "New tag {} found on the Git repo.{}" \
.format(tag, msg_last)
for user in package.maintainers:
addSystemNotification(user, NotificationType.BOT,
msg, url_for("todo.view_user", username=user.username, _external=False), package)
config.last_commit = commit
config.last_tag = tag
db.session.commit()
@celery.task(bind=True)
def check_update_config(self, package_id):
package: Package = Package.query.get(package_id)
@ -305,14 +384,9 @@ def check_update_config(self, package_id):
elif package.update_config is None:
raise TaskError("No update config attached to package")
config = package.update_config
if config.trigger != PackageUpdateTrigger.COMMIT:
return
err = None
try:
hash = get_commit_hash(package.repo, package.update_config.ref)
check_update_config_impl(package)
except IndexError as e:
err = "Unable to find the reference.\n" + str(e)
except GitCommandError as e:
@ -320,6 +394,8 @@ def check_update_config(self, package_id):
err = e.stderr
except gitdb.exc.BadName as e:
err = "Unable to find the reference " + (package.update_config.ref or "?") + "\n" + e.stderr
except TaskError as e:
err = e.value
if err:
err = err.replace("stderr: ", "") \
@ -327,49 +403,13 @@ def check_update_config(self, package_id):
.strip()
msg = "Error: {}.\n\nTask ID: {}\n\n[Change update configuration]({})" \
.format(err, self.request.id, package.getUpdateConfigURL())
.format(err, self.request.id, package.getUpdateConfigURL())
post_bot_message(package, "Failed to check git repository", msg)
db.session.commit()
return
if config.last_commit == hash:
return
if not config.last_commit:
config.last_commit = hash
db.session.commit()
return
if config.make_release:
rel = PackageRelease()
rel.package = package
rel.title = hash[0:5]
rel.url = ""
rel.task_id = uuid()
db.session.add(rel)
db.session.commit()
makeVCSRelease.apply_async((rel.id, package.update_config.ref), task_id=rel.task_id)
elif not config.outdated:
config.outdated = True
msg_last = ""
if config.last_commit:
msg_last = " The last commit was {}".format(config.last_commit[0:5])
msg = "New commit {} found on the Git repo, is the package outdated?{}" \
.format(hash[0:5], msg_last)
for user in package.maintainers:
addSystemNotification(user, NotificationType.BOT,
msg, url_for("todo.view_user", username=user.username), package)
config.last_commit = hash
db.session.commit()
@celery.task
def check_for_updates():

@ -17,7 +17,11 @@
{% endif %}
<div class="col-sm">
{{ _("Git repo has commit %(ref)s", ref=package.update_config.last_commit[0:5]) }}
{% if package.update_config.trigger == package.update_config.trigger.TAG and package.update_config.last_tag %}
{{ _("New tag: %(tag)s", tag=package.update_config.last_tag) }}
{% else %}
{{ _("Git repo has commit %(ref)s", ref=package.update_config.last_commit[0:5]) }}
{% endif %}
</div>
<div class="col-sm-auto">

@ -10,7 +10,12 @@
{% if package.update_config %}
<p class="alert alert-secondary mb-4">
<a class="float-right btn btn-sm btn-secondary" href="{{ package.getUpdateConfigURL() }}">{{ _("Settings") }}</a>
{{ _("You have automatic releases enabled") }}
{% if package.update_config.make_release %}
{{ _("You have automatic releases enabled.") }}
{% else %}
{{ _("You have Git update notifications enabled.") }}
{{ _("You can enable automatic updates in the update detection settings.") }}
{% endif %}
</p>
{% else %}
<p class="alert alert-info mb-4">

@ -31,19 +31,18 @@
{{ render_field(form.trigger, class_="mt-5") }}
<p class="text-warning mb-4">
<small>
<span class="fas fa-exclamation-triangle mr-2"></span>
{{ render_field(form.ref, placeholder="Leave blank to use default branch",
pattern="[A-Za-z0-9/._-]+") }}
{{ _("The new tag trigger isn't supported yet.") }}
{{ _("If you choose it, it won't trigger until it is supported.") }}
{{ _("In the meantime, you can use webhooks to create releases on tag events.") }}
<p class="text-muted mb-4 mt-0">
<small>
<span class="fas fa-info mx-1"></span>
{{ _("Currently, the branch name field is only used by the New Commit trigger.") }}
</small>
</p>
{{ render_field(form.ref, placeholder="Leave blank to use default branch",
pattern="[A-Za-z0-9/._-]+") }}
{{ render_field(form.action) }}
<p class="mt-5">

@ -0,0 +1,32 @@
"""empty message
Revision ID: a337bcc165c0
Revises: f565dde93553
Create Date: 2021-01-29 21:30:37.277197
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'a337bcc165c0'
down_revision = 'f565dde93553'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('package_update_config', sa.Column('outdated_at', sa.DateTime(), nullable=True))
op.add_column('package_update_config', sa.Column('last_tag', sa.String(length=41), nullable=True))
op.drop_column('package_update_config', 'outdated')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('package_update_config', sa.Column('outdated', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))
op.drop_column('package_update_config', 'outdated_at')
op.drop_column('package_update_config', 'last_tag')
# ### end Alembic commands ###