aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/notmuchworker.cpp22
-rw-r--r--src/notmuchworker.h9
-rw-r--r--tests/test_notmuchworker.cpp34
3 files changed, 65 insertions, 0 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp
index 2588f4e..d41b54b 100644
--- a/src/notmuchworker.cpp
+++ b/src/notmuchworker.cpp
@@ -41,6 +41,7 @@
#include "maildirname.h"
#include "mimeparser.h"
#include "nmraii.h"
+#include "searchterm.h"
namespace {
@@ -1082,6 +1083,27 @@ void NotmuchWorker::applyTagsToThreads(const QStringList &threadIds,
applyTags(TagChange{ messageIds, add, remove, description });
}
+void NotmuchWorker::applyTagsToQuery(const QString &query,
+ const QStringList &add,
+ const QStringList &remove,
+ const QString &description)
+{
+ if (!openReadOnly())
+ return;
+
+ QStringList messageIds;
+ if (!collectMessageIds(m_db, SearchTerm::changes(query, add, remove),
+ &messageIds)) {
+ emit errorOccurred(QStringLiteral("Cannot resolve the rule's query"));
+ return;
+ }
+
+ // Empty is not an error: the rule has nothing left to do. applyTags()
+ // returns at once on an empty list, and stays silent, as it must
+ // (CLAUDE.md, item 176).
+ applyTags(TagChange{ messageIds, add, remove, description });
+}
+
void NotmuchWorker::applyTags(const TagChange &change)
{
if (change.messageIds.isEmpty())
diff --git a/src/notmuchworker.h b/src/notmuchworker.h
index ad5f616..fa223e6 100644
--- a/src/notmuchworker.h
+++ b/src/notmuchworker.h
@@ -232,6 +232,15 @@ public slots:
const QStringList &remove,
const QString &description);
+ /// Applies `+add -remove` to every message the change would move within
+ /// `query` (item 212, a rule applied to existing mail). The query is
+ /// narrowed with SearchTerm::changes() so already-correct messages are
+ /// never written, then the ids go through applyTags(), which reports the
+ /// effective set for undo. Emits nothing when nothing would change.
+ void applyTagsToQuery(const QString &query, const QStringList &add,
+ const QStringList &remove,
+ const QString &description);
+
/// Resolves whole threads to the message ids and file paths they contain.
///
/// Delete thread MOVES every message, and a move needs message ids, which
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp
index 10f8dbc..9b64f8e 100644
--- a/tests/test_notmuchworker.cpp
+++ b/tests/test_notmuchworker.cpp
@@ -59,6 +59,7 @@ private slots:
void aThreadCarriesItsCardMessagesOwnTags();
void applyTagsToThreadsTagsEveryMessage();
+ void applyTagsToQueryWritesOnlyWhatWouldChange();
void applyTagsToThreadsSpansMultipleThreads();
void applyTagsToThreadsWithNoThreadsDoesNothing();
@@ -1290,6 +1291,39 @@ void TestNotmuchWorker::applyTagsToThreadsTagsEveryMessage()
restore.applyTags(emitted.inverted());
}
+void TestNotmuchWorker::applyTagsToQueryWritesOnlyWhatWouldChange()
+{
+ // Item 212. Two messages in OPPOSITE states (item 87's lesson): a1
+ // already carries the tag, a2 does not. Only a2 may be reported, or the
+ // undo would strip the tag from a message that had it before.
+ const QString threadId = threadIdOf(QStringLiteral("Release notes"));
+ QVERIFY(!threadId.isEmpty());
+
+ NotmuchWorker setup(m_fixture.configPath());
+ setup.applyTags(TagChange{ { QStringLiteral("a1@example.org") },
+ { QStringLiteral("backfill") }, {},
+ QStringLiteral("seed") });
+
+ NotmuchWorker worker(m_fixture.configPath());
+ QSignalSpy applied(&worker, &NotmuchWorker::tagsApplied);
+ worker.applyTagsToQuery(QStringLiteral("thread:%1").arg(threadId),
+ { QStringLiteral("backfill") }, {},
+ QStringLiteral("Apply rule"));
+
+ QCOMPARE(applied.size(), 1);
+ const TagChange emitted = applied.first().at(0).value<TagChange>();
+ QCOMPARE(emitted.messageIds, QStringList{ QStringLiteral("a2@example.org") });
+ QCOMPARE(emitted.description, QStringLiteral("Apply rule"));
+ QVERIFY(tagsOf(QStringLiteral("a2@example.org"))
+ .contains(QStringLiteral("backfill")));
+
+ NotmuchWorker restore(m_fixture.configPath());
+ restore.applyTags(TagChange{ { QStringLiteral("a1@example.org"),
+ QStringLiteral("a2@example.org") },
+ {}, { QStringLiteral("backfill") },
+ QStringLiteral("restore") });
+}
+
void TestNotmuchWorker::applyTagsToThreadsSpansMultipleThreads()
{
const QString threadA = threadIdOf(QStringLiteral("Release notes"));