[PATCH 0/2] Mention users' relationship to packages in comment notifications
Generally, it's useful to distinguish actionable vs. non-actionable notifications. When receiving a comment notification on a package a user maintains, it can be easy to miss it in a flood of comment notifications for packages the user merely follows. To this end, this patch series adds code to mention the user's relationship to the package when sending a comment notification email. Comments: - I'm not too happy about how intrusive the changes in the first refactoring comment are. Using a Python dictionary instead of introducing a new class would have allowed to avoid declaring the new classes, but that's not where the bulk of changes is. Much of the diff is cluttered from rewrapping to fit within 80 columns. Open to suggestions. - We could also do this for other notification types, but the comment notification is by far the most common one, so I've implemented this only for comment notifications for now. Should we decide otherwise, it is doable without introducing or updating translatable strings. - I'm not including .pot changes, as I've been told this is not being done simultaneously with changes that affect it; though, I've checked the diff after `make update-pot` and it looks okay to me. Vladimir Panteleev (2): notify.py: Encapsulate individual message recipient into a new class notify.py: Mention users' relationship to packages in comment notifications aurweb/scripts/notify.py | 216 ++++++++++++++++++++++++++------------- test/t2500-notify.sh | 150 +++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 73 deletions(-) -- 2.19.0
Allow Notification implementations to add additional fields to their returned Recipient objects, so as to allow customizing the message body/subject per recipient. --- aurweb/scripts/notify.py | 156 ++++++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 69 deletions(-) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index 44eec84..c463823 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -41,6 +41,14 @@ def pkgbase_from_pkgreq(conn, reqid): return cur.fetchone()[0] +class Recipient: + def __init__(self, to, lang): + self._to, self._lang = to, lang + + def get_to(self): + return self._to + + class Notification: def __init__(self): self._l10n = aurweb.l10n.Translator() @@ -51,9 +59,9 @@ class Notification: def get_headers(self): return {} - def get_body_fmt(self, lang): + def get_body_fmt(self, recipient): body = '' - for line in self.get_body(lang).splitlines(): + for line in self.get_body(recipient).splitlines(): body += textwrap.fill(line, break_long_words=False) + '\n' for i, ref in enumerate(self.get_refs()): body += '\n' + '[%d] %s' % (i + 1, ref) @@ -65,10 +73,10 @@ class Notification: reply_to = aurweb.config.get('notifications', 'reply-to') for recipient in self.get_recipients(): - to, lang = recipient - msg = email.mime.text.MIMEText(self.get_body_fmt(lang), + to = recipient.get_to() + msg = email.mime.text.MIMEText(self.get_body_fmt(recipient), 'plain', 'utf-8') - msg['Subject'] = self.get_subject(lang) + msg['Subject'] = self.get_subject(recipient) msg['From'] = sender msg['Reply-to'] = reply_to msg['To'] = to @@ -89,34 +97,34 @@ class ResetKeyNotification(Notification): super().__init__() def get_recipients(self): - return [(self._to, self._lang)] + return [Recipient(self._to, self._lang)] - def get_subject(self, lang): - return self._l10n.translate('AUR Password Reset', lang) + def get_subject(self, recipient): + return self._l10n.translate('AUR Password Reset', recipient._lang) - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'A password reset request was submitted for the account ' '{user} associated with your email address. If you wish to ' 'reset your password follow the link [1] below, otherwise ' 'ignore this message and nothing will happen.', - lang).format(user=self._username) + recipient._lang).format(user=self._username) def get_refs(self): return (aur_location + '/passreset/?resetkey=' + self._resetkey,) class WelcomeNotification(ResetKeyNotification): - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('Welcome to the Arch User Repository', - lang) + recipient._lang) - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'Welcome to the Arch User Repository! In order to set an ' 'initial password for your new account, please click the ' 'link [1] below. If the link does not work, try copying and ' - 'pasting it into your browser.', lang) + 'pasting it into your browser.', recipient._lang) class CommentNotification(Notification): @@ -130,7 +138,7 @@ class CommentNotification(Notification): 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?', [comment_id]) self._text = cur.fetchone()[0] @@ -139,20 +147,22 @@ class CommentNotification(Notification): def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Comment for {pkgbase}', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format( + pkgbase=self._pkgbase) - def get_body(self, lang): + def get_body(self, recipient): body = self._l10n.translate( '{user} [1] added the following comment to {pkgbase} [2]:', - lang).format(user=self._user, pkgbase=self._pkgbase) + recipient._lang).format(user=self._user, pkgbase=self._pkgbase) body += '\n\n' + self._text + '\n\n' - dnlabel = self._l10n.translate('Disable notifications', lang) + dnlabel = self._l10n.translate('Disable notifications', + recipient._lang) body += self._l10n.translate( 'If you no longer wish to receive notifications about this ' 'package, please go to the package page [2] and select ' - '"{label}".', lang).format(label=dnlabel) + '"{label}".', recipient._lang).format(label=dnlabel) return body def get_refs(self): @@ -177,27 +187,29 @@ class UpdateNotification(Notification): 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] super().__init__() def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Package Update: {pkgbase}', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format( + pkgbase=self._pkgbase) - def get_body(self, lang): + def get_body(self, recipient): body = self._l10n.translate('{user} [1] pushed a new commit to ' - '{pkgbase} [2].', lang).format( + '{pkgbase} [2].', recipient._lang).format( user=self._user, pkgbase=self._pkgbase) body += '\n\n' - dnlabel = self._l10n.translate('Disable notifications', lang) + dnlabel = self._l10n.translate('Disable notifications', + recipient._lang) body += self._l10n.translate( 'If you no longer wish to receive notifications about this ' 'package, please go to the package page [2] and select ' - '"{label}".', lang).format(label=dnlabel) + '"{label}".', recipient._lang).format(label=dnlabel) return body def get_refs(self): @@ -222,7 +234,7 @@ class FlagNotification(Notification): 'ON PackageBases.MaintainerUID = Users.ID OR ' + 'PackageBases.ID = PackageComaintainers.PackageBaseID ' + 'WHERE PackageBases.ID = ?', [pkgbase_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + 'ID = ?', [pkgbase_id]) self._text = cur.fetchone()[0] @@ -231,16 +243,17 @@ class FlagNotification(Notification): def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Out-of-date Notification for ' '{pkgbase}', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format( + pkgbase=self._pkgbase) - def get_body(self, lang): + def get_body(self, recipient): body = self._l10n.translate( 'Your package {pkgbase} [1] has been flagged out-of-date by ' - '{user} [2]:', lang).format(pkgbase=self._pkgbase, - user=self._user) + '{user} [2]:', recipient._lang).format(pkgbase=self._pkgbase, + user=self._user) body += '\n\n' + self._text return body @@ -261,7 +274,7 @@ class OwnershipEventNotification(Notification): 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' + 'ID = ?', [pkgbase_id]) self._text = cur.fetchone()[0] @@ -270,9 +283,10 @@ class OwnershipEventNotification(Notification): def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Ownership Notification for {pkgbase}', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format( + pkgbase=self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/', @@ -280,18 +294,18 @@ class OwnershipEventNotification(Notification): class AdoptNotification(OwnershipEventNotification): - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'The package {pkgbase} [1] was adopted by {user} [2].', - lang).format(pkgbase=self._pkgbase, user=self._user) + recipient._lang).format(pkgbase=self._pkgbase, user=self._user) class DisownNotification(OwnershipEventNotification): - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'The package {pkgbase} [1] was disowned by {user} ' - '[2].', lang).format(pkgbase=self._pkgbase, - user=self._user) + '[2].', recipient._lang).format(pkgbase=self._pkgbase, + user=self._user) class ComaintainershipEventNotification(Notification): @@ -303,29 +317,30 @@ class ComaintainershipEventNotification(Notification): super().__init__() def get_recipients(self): - return [(self._to, self._lang)] + return [Recipient(self._to, self._lang)] - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Co-Maintainer Notification for ' '{pkgbase}', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format( + pkgbase=self._pkgbase) def get_refs(self): return (aur_location + '/pkgbase/' + self._pkgbase + '/',) class ComaintainerAddNotification(ComaintainershipEventNotification): - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'You were added to the co-maintainer list of {pkgbase} [1].', - lang).format(pkgbase=self._pkgbase) + recipient._lang).format(pkgbase=self._pkgbase) class ComaintainerRemoveNotification(ComaintainershipEventNotification): - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'You were removed from the co-maintainer list of {pkgbase} ' - '[1].', lang).format(pkgbase=self._pkgbase) + '[1].', recipient._lang).format(pkgbase=self._pkgbase) class DeleteNotification(Notification): @@ -343,31 +358,34 @@ class DeleteNotification(Notification): 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, old_pkgbase_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] super().__init__() def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('AUR Package deleted: {pkgbase}', - lang).format(pkgbase=self._old_pkgbase) + recipient._lang).format( + pkgbase=self._old_pkgbase) - def get_body(self, lang): + def get_body(self, recipient): if self._new_pkgbase: - dnlabel = self._l10n.translate('Disable notifications', lang) + dnlabel = self._l10n.translate('Disable notifications', + recipient._lang) return self._l10n.translate( '{user} [1] merged {old} [2] into {new} [3].\n\n' 'If you no longer wish receive notifications about the ' 'new package, please go to [3] and click "{label}".', - lang).format(user=self._user, old=self._old_pkgbase, - new=self._new_pkgbase, label=dnlabel) + recipient._lang).format( + user=self._user, old=self._old_pkgbase, + new=self._new_pkgbase, label=dnlabel) else: return self._l10n.translate( '{user} [1] deleted {pkgbase} [2].\n\n' 'You will no longer receive notifications about this ' - 'package.', lang).format(user=self._user, - pkgbase=self._old_pkgbase) + 'package.', recipient._lang).format( + user=self._user, pkgbase=self._old_pkgbase) def get_refs(self): refs = (aur_location + '/account/' + self._user + '/', @@ -398,13 +416,13 @@ class RequestOpenNotification(Notification): self._merge_into = merge_into def get_recipients(self): - return [(self._to, 'en')] + return [Recipient(self._to, 'en')] - def get_subject(self, lang): + def get_subject(self, recipient): return '[PRQ#%d] %s Request for %s' % \ (self._reqid, self._reqtype.title(), self._pkgbase) - def get_body(self, lang): + def get_body(self, recipient): if self._merge_into: body = '%s [1] filed a request to merge %s [2] into %s [3]:' % \ (self._user, self._pkgbase, self._merge_into) @@ -455,15 +473,15 @@ class RequestCloseNotification(Notification): self._reason = reason def get_recipients(self): - return [(self._to, 'en')] + return [Recipient(self._to, 'en')] - def get_subject(self, lang): + def get_subject(self, recipient): return '[PRQ#%d] %s Request for %s %s' % (self._reqid, self._reqtype.title(), self._pkgbase, self._reason.title()) - def get_body(self, lang): + def get_body(self, recipient): if self._user: body = 'Request #%d has been %s by %s [1]' % \ (self._reqid, self._reason, self._user) @@ -497,21 +515,21 @@ class TUVoteReminderNotification(Notification): 'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' + '(SELECT UserID FROM TU_Votes ' + 'WHERE TU_Votes.VoteID = ?)', [vote_id]) - self._recipients = cur.fetchall() + self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] super().__init__() def get_recipients(self): return self._recipients - def get_subject(self, lang): + def get_subject(self, recipient): return self._l10n.translate('TU Vote Reminder: Proposal {id}', - lang).format(id=self._vote_id) + recipient._lang).format(id=self._vote_id) - def get_body(self, lang): + def get_body(self, recipient): return self._l10n.translate( 'Please remember to cast your vote on proposal {id} [1]. ' 'The voting period ends in less than 48 hours.', - lang).format(id=self._vote_id) + recipient._lang).format(id=self._vote_id) def get_refs(self): return (aur_location + '/tu/?id=' + str(self._vote_id),) -- 2.19.0
Generally, it's useful to distinguish actionable vs. non-actionable notifications. When receiving a comment notification on a package a user maintains, it can be easy to miss it in a flood of comment notifications for packages the user merely follows. To this end, mention the user's relationship to the package when sending a comment notification email. --- aurweb/scripts/notify.py | 66 +++++++++++++++-- test/t2500-notify.sh | 150 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 7 deletions(-) diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py index c463823..30eb0c3 100755 --- a/aurweb/scripts/notify.py +++ b/aurweb/scripts/notify.py @@ -127,18 +127,65 @@ class WelcomeNotification(ResetKeyNotification): 'pasting it into your browser.', recipient._lang) +class CommentRecipient(Recipient): + def __init__(self, to, lang, role_short, role_long): + self._role_short = role_short + self._role_long = role_long + super().__init__(to, lang) + + class CommentNotification(Notification): def __init__(self, conn, uid, pkgbase_id, comment_id): self._user = username_from_id(conn, uid) - self._pkgbase = pkgbase_from_id(conn, pkgbase_id) - cur = conn.execute('SELECT DISTINCT Users.Email, Users.LangPreference ' + cur = conn.execute('SELECT Name, FlaggerUID, SubmitterUID, ' + + 'MaintainerUID, PackagerUID FROM PackageBases ' + + 'WHERE ID = ?', [pkgbase_id]) + (self._pkgbase, + flagger_id, submitter_id, maintainer_id, packager_id) = cur.fetchone() + + cur = conn.execute('SELECT DISTINCT Users.ID, Users.Email, ' + + 'Users.LangPreference, ' + + 'COUNT(PackageComaintainers.UsersID) ' + 'FROM Users INNER JOIN PackageNotifications ' + - 'ON PackageNotifications.UserID = Users.ID WHERE ' + + 'ON PackageNotifications.UserID = Users.ID ' + + 'LEFT JOIN PackageComaintainers ' + + 'ON PackageComaintainers.UsersID = Users.ID ' + + 'AND PackageComaintainers.PackageBaseID = ' + + 'PackageNotifications.PackageBaseID WHERE ' + 'Users.CommentNotify = 1 AND ' + 'PackageNotifications.UserID != ? AND ' + 'PackageNotifications.PackageBaseID = ?', [uid, pkgbase_id]) - self._recipients = [Recipient(to, lang) for to, lang in cur.fetchall()] + recipients = [] + for (user_id, to, lang, is_comaintainer) in cur: + if user_id == maintainer_id: + role_short = self._l10n.translate(' (maintained by you)', lang) + role_long = self._l10n.translate( + 'You are currently maintaining this package.', lang) + elif is_comaintainer > 0: + role_short = self._l10n.translate( + ' (co-maintained by you)', lang) + role_long = self._l10n.translate( + 'You are currently co-maintaining this package.', lang) + elif user_id == packager_id: + role_short = self._l10n.translate(' (packaged by you)', lang) + role_long = self._l10n.translate( + 'You are this package\'s last packager.', lang) + elif user_id == submitter_id: + role_short = self._l10n.translate(' (submitted by you)', lang) + role_long = self._l10n.translate( + 'You are this package\'s submitter.', lang) + elif user_id == flagger_id: + role_short = self._l10n.translate(' (flagged by you)', lang) + role_long = self._l10n.translate( + 'You have last flagged this package.', lang) + else: + role_short = None + role_long = None + recipients.append(CommentRecipient(to, lang, + role_short, role_long)) + self._recipients = recipients + cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?', [comment_id]) self._text = cur.fetchone()[0] @@ -148,15 +195,20 @@ class CommentNotification(Notification): return self._recipients def get_subject(self, recipient): - return self._l10n.translate('AUR Comment for {pkgbase}', - recipient._lang).format( - pkgbase=self._pkgbase) + subject = self._l10n.translate('AUR Comment for {pkgbase}', + recipient._lang).format( + pkgbase=self._pkgbase) + if recipient._role_short: + subject += recipient._role_short + return subject def get_body(self, recipient): body = self._l10n.translate( '{user} [1] added the following comment to {pkgbase} [2]:', recipient._lang).format(user=self._user, pkgbase=self._pkgbase) body += '\n\n' + self._text + '\n\n' + if recipient._role_long: + body += recipient._role_long + '\n\n' dnlabel = self._l10n.translate('Disable notifications', recipient._lang) body += self._l10n.translate( diff --git a/test/t2500-notify.sh b/test/t2500-notify.sh index c9c7208..c8936e0 100755 --- a/test/t2500-notify.sh +++ b/test/t2500-notify.sh @@ -110,6 +110,156 @@ test_expect_success 'Test subject and body of comment notifications.' ' test_cmp actual expected ' +test_expect_success 'Test subject and body of comment notifications for flaggers.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE PackageBases SET FlaggerUID = 2 WHERE ID == 1001; + EOD + >sendmail.out && + "$NOTIFY" comment 1 1001 2001 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar (flagged by you) + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + You have last flagged this package. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of comment notifications for submitters.' ' + cat <<-EOD | sqlite3 aur.db && + /* Keep and accumulate FlaggerUID and other properties from preceding tests. */ + /* Only the most "important" relationship should be mentioned. */ + UPDATE PackageBases SET SubmitterUID = 2 WHERE ID == 1001; + EOD + >sendmail.out && + "$NOTIFY" comment 1 1001 2001 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar (submitted by you) + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + You are this package'\''s submitter. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of comment notifications for packagers.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE PackageBases SET PackagerUID = 2 WHERE ID == 1001; + EOD + >sendmail.out && + "$NOTIFY" comment 1 1001 2001 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar (packaged by you) + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + You are this package'\''s last packager. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of comment notifications for co-maintainers.' ' + cat <<-EOD | sqlite3 aur.db && + INSERT INTO PackageComaintainers (PackageBaseID, UsersID, Priority) VALUES (1001, 2, 1); + EOD + >sendmail.out && + "$NOTIFY" comment 1 1001 2001 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar (co-maintained by you) + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + You are currently co-maintaining this package. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected +' + +test_expect_success 'Test subject and body of comment notifications for maintainers.' ' + cat <<-EOD | sqlite3 aur.db && + UPDATE PackageBases SET MaintainerUID = 2 WHERE ID == 1001; + EOD + >sendmail.out && + "$NOTIFY" comment 1 1001 2001 && + grep ^Subject: sendmail.out >actual && + cat <<-EOD >expected && + Subject: AUR Comment for foobar (maintained by you) + EOD + test_cmp actual expected && + sed -n "/^\$/,\$p" sendmail.out | base64 -d >actual && + echo >>actual && + cat <<-EOD >expected && + user [1] added the following comment to foobar [2]: + + This is a test comment. + + You are currently maintaining this package. + + If you no longer wish to receive notifications about this package, + please go to the package page [2] and select "Disable notifications". + + [1] https://aur.archlinux.org/account/user/ + [2] https://aur.archlinux.org/pkgbase/foobar/ + EOD + test_cmp actual expected + cat <<-EOD | sqlite3 aur.db + DELETE FROM PackageComaintainers; + EOD +' + test_expect_success 'Test subject and body of update notifications.' ' cat <<-EOD | sqlite3 aur.db && UPDATE Users SET UpdateNotify = 1 WHERE ID = 2; -- 2.19.0
participants (1)
-
Vladimir Panteleev