reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 4eca19f3eeac488bbef020e8bf88df3ef7a30a58
parent 9070d20b9a7cdfb99ed6fbeb6902341aa2184d7a
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Tue, 19 Jan 2016 01:16:29 -0500

implement full remote repository uninstallation

Diffstat:
Msrc/database.cpp | 6++++++
Msrc/database.hpp | 1+
Msrc/manager.cpp | 1+
Msrc/path.cpp | 5+++++
Msrc/path.hpp | 1+
Msrc/progress.cpp | 7++++++-
Msrc/reapack.cpp | 18+++++++++++++++---
Msrc/reapack.hpp | 1+
Msrc/registry.cpp | 32++++++++++++++++++++++++++++++++
Msrc/registry.hpp | 12++++++++++--
Msrc/report.cpp | 63+++++++++++++++++++++++++++++++++++++++------------------------
Msrc/report.hpp | 13+++++++++----
Msrc/resource.rc | 3++-
Msrc/task.cpp | 133++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/task.hpp | 51++++++++++++++++++++++++++++++++++++++++++---------
Msrc/transaction.cpp | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/transaction.hpp | 10+++++++++-
Mtest/path.cpp | 13+++++++++++++
Mtest/registry.cpp | 34+++++++++++++++++++++++++++++++++-
19 files changed, 365 insertions(+), 119 deletions(-)

diff --git a/src/database.cpp b/src/database.cpp @@ -114,6 +114,12 @@ void Statement::bind(const int index, const string &text) throw m_db->lastError(); } +void Statement::bind(const int index, const int integer) +{ + if(sqlite3_bind_int(m_stmt, index, integer)) + throw m_db->lastError(); +} + void Statement::bind(const int index, const uint64_t integer) { if(sqlite3_bind_int64(m_stmt, index, (sqlite3_int64)integer)) diff --git a/src/database.hpp b/src/database.hpp @@ -56,6 +56,7 @@ public: typedef std::function<bool (void)> ExecCallback; void bind(const int index, const std::string &text); + void bind(const int index, const int integer); void bind(const int index, const uint64_t integer); void exec(); void exec(const ExecCallback &); diff --git a/src/manager.cpp b/src/manager.cpp @@ -184,6 +184,7 @@ void Manager::apply() } for(const Remote &remote : m_uninstall) { + m_reapack->uninstall(remote); list->remove(remote); } diff --git a/src/path.cpp b/src/path.cpp @@ -81,6 +81,11 @@ void Path::clear() m_parts.clear(); } +void Path::removeLast() +{ + m_parts.pop_back(); +} + string Path::basename() const { if(m_parts.empty()) diff --git a/src/path.hpp b/src/path.hpp @@ -27,6 +27,7 @@ public: void prepend(const std::string &part); void append(const std::string &part); + void removeLast(); void clear(); bool empty() const { return m_parts.empty(); } diff --git a/src/progress.cpp b/src/progress.cpp @@ -38,8 +38,10 @@ void Progress::setTransaction(Transaction *t) m_total = 0; m_currentName.clear(); - if(!m_transaction) + if(!m_transaction) { + hide(); return; + } SetWindowText(m_label, AUTO_STR("Initializing...")); @@ -73,6 +75,9 @@ void Progress::addDownload(Download *dl) updateProgress(); dl->onStart([=] { + if(!isVisible()) + show(); + m_currentName = make_autostring(dl->name()); updateProgress(); }); diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -104,6 +104,7 @@ void ReaPack::synchronizeAll() return; } + // do nothing is a transation is already running Transaction *t = createTransaction(); if(!t) @@ -124,6 +125,19 @@ void ReaPack::synchronize(const Remote &remote) m_transaction->synchronize(remote); } +void ReaPack::uninstall(const Remote &remote) +{ + if(remote.isProtected()) + return; + + if(!m_transaction) { + if(!createTransaction()) + return; + } + + m_transaction->uninstall(remote); +} + void ReaPack::importRemote() { static const char *title = "ReaPack: Import remote repository"; @@ -209,7 +223,6 @@ Transaction *ReaPack::createTransaction() } m_progress->setTransaction(m_transaction); - m_progress->show(); m_transaction->onFinish([=] { if(m_transaction->isCancelled()) @@ -217,13 +230,12 @@ Transaction *ReaPack::createTransaction() m_progress->disable(); - if(m_transaction->packages().empty() && m_transaction->errors().empty()) + if(m_transaction->taskCount() == 0 && m_transaction->errors().empty()) ShowMessageBox("Nothing to do!", "ReaPack", 0); else Dialog::Show<Report>(m_instance, m_mainWindow, m_transaction); m_progress->enable(); - m_progress->hide(); }); m_transaction->onDestroy([=] { diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -49,6 +49,7 @@ public: void synchronizeAll(); void synchronize(const Remote &); + void uninstall(const Remote &); void importRemote(); void manageRemotes(); diff --git a/src/registry.cpp b/src/registry.cpp @@ -21,6 +21,7 @@ #include "index.hpp" #include "package.hpp" #include "path.hpp" +#include "remote.hpp" #include <reaper_plugin_functions.h> @@ -31,6 +32,7 @@ Registry::Registry(const Path &path) { migrate(); + // entry queries m_insertEntry = m_db.prepare( "INSERT OR REPLACE INTO entries " "VALUES(NULL, ?, ?, ?, ?);" @@ -42,6 +44,10 @@ Registry::Registry(const Path &path) "LIMIT 1" ); + m_allEntries = m_db.prepare("SELECT id, version FROM entries WHERE remote = ?"); + m_forgetEntry = m_db.prepare("DELETE FROM entries WHERE id = ?"); + + // file queries m_getFiles = m_db.prepare("SELECT path FROM files WHERE entry = ?"); m_insertFile = m_db.prepare("INSERT INTO files VALUES(NULL, ?, ?)"); m_clearFiles = m_db.prepare( @@ -49,6 +55,7 @@ Registry::Registry(const Path &path) " SELECT id FROM entries WHERE remote = ? AND category = ? AND package = ?" ")" ); + m_forgetFiles = m_db.prepare("DELETE FROM files WHERE entry = ?"); // lock the database m_db.begin(); @@ -144,6 +151,22 @@ Registry::Entry Registry::query(Package *pkg) const return {id, status, version}; } +vector<Registry::Entry> Registry::queryAll(const Remote &remote) const +{ + vector<Registry::Entry> list; + + m_allEntries->bind(1, remote.name()); + m_allEntries->exec([&] { + const int id = m_allEntries->intColumn(0); + const uint64_t version = m_allEntries->uint64Column(1); + + list.push_back({id, Unknown, version}); + return true; + }); + + return list; +} + set<Path> Registry::getFiles(const Entry &qr) const { if(!qr.id) // skip processing for new packages @@ -160,6 +183,15 @@ set<Path> Registry::getFiles(const Entry &qr) const return list; } +void Registry::forget(const Entry &qr) +{ + m_forgetFiles->bind(1, qr.id); + m_forgetFiles->exec(); + + m_forgetEntry->bind(1, qr.id); + m_forgetEntry->exec(); +} + void Registry::commit() { m_db.commit(); diff --git a/src/registry.hpp b/src/registry.hpp @@ -26,6 +26,7 @@ class Package; class Path; +class Remote; class Version; class Registry { @@ -33,9 +34,10 @@ public: Registry(const Path &path = Path()); enum Status { - UpToDate, - UpdateAvailable, + Unknown, Uninstalled, + UpdateAvailable, + UpToDate, }; struct Entry { @@ -45,8 +47,10 @@ public: }; Entry query(Package *) const; + std::vector<Entry> queryAll(const Remote &) const; std::set<Path> getFiles(const Entry &) const; void push(Version *); + void forget(const Entry &); void commit(); bool addToREAPER(Version *ver, const Path &root); @@ -57,9 +61,13 @@ private: Database m_db; Statement *m_insertEntry; Statement *m_findEntry; + Statement *m_allEntries; + Statement *m_forgetEntry; + Statement *m_getFiles; Statement *m_insertFile; Statement *m_clearFiles; + Statement *m_forgetFiles; }; #endif diff --git a/src/report.cpp b/src/report.cpp @@ -23,7 +23,6 @@ #include "transaction.hpp" #include <boost/range/adaptor/reversed.hpp> -#include <sstream> using namespace std; @@ -37,29 +36,32 @@ Report::Report(Transaction *transaction) void Report::onInit() { - const size_t newPacks = m_transaction->newPackages().size(); + const size_t newPackages = m_transaction->newPackages().size(); const size_t updates = m_transaction->updates().size(); + const size_t removals = m_transaction->removals().size(); const size_t errors = m_transaction->errors().size(); - ostringstream text; - - text - << newPacks << " new packages, " - << updates << " updates and " + m_stream + << newPackages << " new packages, " + << updates << " updates, " + << removals << " removed files and " << errors << " errors" << NL ; if(errors) - formatErrors(text); + printErrors(); - if(newPacks) - formatNewPackages(text); + if(newPackages) + printNewPackages(); if(updates) - formatUpdates(text); + printUpdates(); + + if(removals) + printRemovals(); - const auto_string &str = make_autostring(text.str()); + const auto_string &str = make_autostring(m_stream.str()); SetDlgItemText(handle(), IDC_REPORT, str.c_str()); } @@ -73,19 +75,19 @@ void Report::onCommand(WPARAM wParam, LPARAM) } } -void Report::formatNewPackages(ostringstream &text) +void Report::printNewPackages() { - text << NL << SEP << " New packages: " << SEP << NL; + printHeader("New packages"); for(const Transaction::PackageEntry &entry : m_transaction->newPackages()) { Version *ver = entry.first; - text << NL << ver->fullName() << NL; + m_stream << NL << ver->fullName() << NL; } } -void Report::formatUpdates(ostringstream &text) +void Report::printUpdates() { - text << NL << SEP << " Updates: " << SEP << NL; + printHeader("Updates"); for(const Transaction::PackageEntry &entry : m_transaction->updates()) { Package *pkg = entry.first->package(); @@ -96,27 +98,40 @@ void Report::formatUpdates(ostringstream &text) if(ver->code() <= regEntry.version) break; - text << NL << ver->fullName() << NL; + m_stream << NL << ver->fullName() << NL; if(!ver->changelog().empty()) - formatChangelog(ver->changelog(), text); + printChangelog(ver->changelog()); } } } -void Report::formatChangelog(const string &changelog, ostringstream &output) +void Report::printChangelog(const string &changelog) { istringstream input(changelog); string line; while(getline(input, line, '\n')) - output << " " << line.substr(line.find_first_not_of('\x20')) << NL; + m_stream << "\x20\x20" << line.substr(line.find_first_not_of('\x20')) << NL; } -void Report::formatErrors(ostringstream &text) +void Report::printErrors() { - text << NL << SEP << " Errors: " << SEP << NL; + printHeader("Errors"); for(const Transaction::Error &err : m_transaction->errors()) - text << NL << err.title << ":" << NL << err.message << NL; + m_stream << NL << err.title << ':' << NL << err.message << NL; +} + +void Report::printRemovals() +{ + printHeader("Removed files"); + + for(const Path &path : m_transaction->removals()) + m_stream << NL << path.join(); +} + +void Report::printHeader(const char *title) +{ + m_stream << NL << SEP << ' ' << title << ": " << SEP << NL; } diff --git a/src/report.hpp b/src/report.hpp @@ -20,6 +20,8 @@ #include "dialog.hpp" +#include <sstream> + class Transaction; class Report : public Dialog { @@ -31,12 +33,15 @@ protected: void onCommand(WPARAM, LPARAM) override; private: - void formatNewPackages(std::ostringstream &); - void formatUpdates(std::ostringstream &); - void formatErrors(std::ostringstream &); - void formatChangelog(const std::string &, std::ostringstream &); + void printNewPackages(); + void printUpdates(); + void printErrors(); + void printRemovals(); + void printChangelog(const std::string &); + void printHeader(const char *); Transaction *m_transaction; + std::ostringstream m_stream; }; #endif diff --git a/src/resource.rc b/src/resource.rc @@ -18,7 +18,8 @@ STYLE DIALOG_STYLE FONT DIALOG_FONT CAPTION "ReaPack: Transaction Report" BEGIN - LTEXT "Synchronization complete!", IDC_LABEL, 5, 5, 250, 10 + LTEXT "All done! Description of the changes:", + IDC_LABEL, 5, 5, 250, 10 EDITTEXT IDC_REPORT, 6, 18, 248, 195, WS_VSCROLL | ES_MULTILINE | ES_READONLY | NOT WS_TABSTOP DEFPUSHBUTTON "OK", IDOK, 105, 220, 50, 14 diff --git a/src/task.cpp b/src/task.cpp @@ -32,7 +32,51 @@ Task::Task(Transaction *transaction) { } -void Task::install(Version *ver, const set<Path> &oldFiles) +void Task::rollback() +{ + m_isCancelled = true; + + // it's the transaction queue's job to abort the running downloads, not ours + + doRollback(); +} + +void Task::commit() +{ + if(m_isCancelled) + return; + + doCommit(); + + m_onCommit(); +} + +int Task::removeFile(const Path &path) const +{ + const string &fullPath = m_transaction->prefixPath(path).join(); + +#ifdef _WIN32 + return _wremove(make_autostring(fullPath).c_str()); +#else + return remove(fullPath.c_str()); +#endif +} + +int Task::renameFile(const Path &from, const Path &to) const +{ + const string &fullFrom = m_transaction->prefixPath(from).join(); + const string &fullTo = m_transaction->prefixPath(to).join(); + +#ifdef _WIN32 + return _wrename(make_autostring(fullFrom).c_str(), + make_autostring(fullTo).c_str()); +#else + return rename(fullFrom.c_str(), fullTo.c_str()); +#endif +} + +InstallTask::InstallTask(Version *ver, const set<Path> &oldFiles, Transaction *t) + : Task(t), m_oldFiles(move(oldFiles)) { const auto &sources = ver->sources(); @@ -41,62 +85,48 @@ void Task::install(Version *ver, const set<Path> &oldFiles) Source *src = it->second; Download *dl = new Download(src->fullName(), src->url()); - dl->onFinish(bind(&Task::saveSource, this, dl, src)); + dl->onFinish(bind(&InstallTask::saveSource, this, dl, src)); - m_transaction->downloadQueue()->push(dl); + transaction()->downloadQueue()->push(dl); // skip duplicate files do { it++; } while(it != sources.end() && path == it->first); } - - m_oldFiles = move(oldFiles); } -void Task::saveSource(Download *dl, Source *src) +void InstallTask::saveSource(Download *dl, Source *src) { - if(m_isCancelled) + if(isCancelled()) return; - const Path targetPath = src->targetPath(); - Path tmpPath = targetPath; + const Path &targetPath = src->targetPath(); + Path tmpPath(targetPath); tmpPath[tmpPath.size() - 1] += ".new"; - m_files.push_back({tmpPath, targetPath}); + m_newFiles.push_back({tmpPath, targetPath}); const auto old = m_oldFiles.find(targetPath); if(old != m_oldFiles.end()) m_oldFiles.erase(old); - const Path path = m_transaction->prefixPath(tmpPath); + const Path &path = transaction()->prefixPath(tmpPath); - if(!m_transaction->saveFile(dl, path)) { - cancel(); + if(!transaction()->saveFile(dl, path)) { + rollback(); return; } } -void Task::cancel() -{ - m_isCancelled = true; - - // it's the transaction queue's job to abort the running downloads, not ours - - rollback(); -} - -void Task::commit() +void InstallTask::doCommit() { - if(m_isCancelled) - return; - for(const Path &path : m_oldFiles) removeFile(path); - for(const PathPair &paths : m_files) { + for(const PathPair &paths : m_newFiles) { removeFile(paths.second); if(renameFile(paths.first, paths.second)) { - m_transaction->addError(strerror(errno), paths.first.join()); + transaction()->addError(strerror(errno), paths.first.join()); // it's a bit late to rollback here as some files might already have been // overwritten. at least we can delete the temporary files @@ -104,38 +134,43 @@ void Task::commit() return; } } - - m_onCommit(); } -void Task::rollback() +void InstallTask::doRollback() { - for(const PathPair &paths : m_files) + for(const PathPair &paths : m_newFiles) removeFile(paths.first); - m_files.clear(); + m_newFiles.clear(); } -int Task::removeFile(const Path &path) const +RemoveTask::RemoveTask(const std::set<Path> &files, Transaction *t) + : Task(t), m_files(move(files)) { - const string &fullPath = m_transaction->prefixPath(path).join(); +} -#ifdef _WIN32 - return _wremove(make_autostring(fullPath).c_str()); -#else - return remove(fullPath.c_str()); -#endif +void RemoveTask::doCommit() +{ + for(const Path &path : m_files) + remove(path); } -int Task::renameFile(const Path &from, const Path &to) const +void RemoveTask::remove(const Path &file) { - const string &fullFrom = m_transaction->prefixPath(from).join(); - const string &fullTo = m_transaction->prefixPath(to).join(); + if(removeFile(file)) { + transaction()->addError(strerror(errno), file.join()); + return; + } + else + m_removedFiles.push_back(file); -#ifdef _WIN32 - return _wrename(make_autostring(fullFrom).c_str(), - make_autostring(fullTo).c_str()); -#else - return rename(fullFrom.c_str(), fullTo.c_str()); -#endif + Path dir = file; + + // remove empty directories, but not top-level ones that were created by REAPER + while(dir.size() > 2) { + dir.removeLast(); + + if(removeFile(dir)) + break; + } } diff --git a/src/task.hpp b/src/task.hpp @@ -34,30 +34,63 @@ public: typedef Signal::slot_type Callback; Task(Transaction *parent); + virtual ~Task() {} void onCommit(const Callback &callback) { m_onCommit.connect(callback); } + bool isCancelled() const { return m_isCancelled; } - void install(Version *ver, const std::set<Path> &oldFiles); void commit(); - void cancel(); + void rollback(); -private: +protected: int removeFile(const Path &) const; int renameFile(const Path &, const Path &) const; - typedef std::pair<Path, Path> PathPair; + Transaction *transaction() const { return m_transaction; } - void finish(); - void rollback(); + virtual void doCommit() = 0; + virtual void doRollback() = 0; - void saveSource(Download *, Source *); +private: Transaction *m_transaction; bool m_isCancelled; - std::vector<PathPair> m_files; - std::set<Path> m_oldFiles; Signal m_onCommit; }; +class InstallTask : public Task { +public: + InstallTask(Version *ver, const std::set<Path> &oldFiles, Transaction *); + +protected: + void doCommit() override; + void doRollback() override; + +private: + typedef std::pair<Path, Path> PathPair; + + void saveSource(Download *, Source *); + + std::vector<PathPair> m_newFiles; + std::set<Path> m_oldFiles; +}; + +class RemoveTask : public Task { +public: + RemoveTask(const std::set<Path> &files, Transaction *); + + const std::vector<Path> &removedFiles() const { return m_removedFiles; } + +protected: + void doCommit() override; + void doRollback() override {} + +private: + void remove(const Path &); + + std::set<Path> m_files; + std::vector<Path> m_removedFiles; +}; + #endif diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -122,33 +122,54 @@ void Transaction::install() for(const PackageEntry &entry : m_packages) { Version *ver = entry.first; const Registry::Entry regEntry = entry.second; + const set<Path> &currentFiles = m_registry->getFiles(regEntry); - Task *task = new Task(this); + InstallTask *task = new InstallTask(ver, currentFiles, this); - try { - task->install(ver, m_registry->getFiles(regEntry)); - task->onCommit([=] { - if(regEntry.status == Registry::UpdateAvailable) - m_updates.push_back(entry); - else - m_new.push_back(entry); + task->onCommit([=] { + if(regEntry.status == Registry::UpdateAvailable) + m_updates.push_back(entry); + else + m_new.push_back(entry); - m_registry->push(ver); + m_registry->push(ver); - if(!m_registry->addToREAPER(ver, m_root)) { - addError( - "Cannot register the package in REAPER. " - "Are you using REAPER v5.12 or more recent?", ver->fullName() - ); - } - }); + if(!m_registry->addToREAPER(ver, m_root)) { + addError( + "Cannot register the package in REAPER. " + "Are you using REAPER v5.12 or more recent?", ver->fullName() + ); + } + }); - m_tasks.push_back(task); - } - catch(const reapack_error &e) { - addError(e.what(), ver->fullName()); - delete task; - } + addTask(task); + } +} + +void Transaction::uninstall(const Remote &remote) +{ + const vector<Registry::Entry> &entries = m_registry->queryAll(remote); + + if(entries.empty()) { + cancel(); + return; + } + + for(const auto &entry : entries) { + const set<Path> &files = m_registry->getFiles(entry); + + RemoveTask *task = new RemoveTask(files, this); + + task->onCommit([=] { + const vector<Path> &removedFiles = task->removedFiles(); + + m_registry->forget(entry); + + m_removals.insert(m_removals.end(), + removedFiles.begin(), removedFiles.end()); + }); + + addTask(task); } } @@ -157,9 +178,12 @@ void Transaction::cancel() m_isCancelled = true; for(Task *task : m_tasks) - task->cancel(); + task->rollback(); - m_queue.abort(); + if(m_queue.idle()) + finish(); + else + m_queue.abort(); } bool Transaction::saveFile(Download *dl, const Path &path) @@ -234,3 +258,11 @@ void Transaction::registerFiles(const set<Path> &list) m_files.insert(list.begin(), list.end()); } + +void Transaction::addTask(Task *task) +{ + m_tasks.push_back(task); + + if(m_queue.idle()) + finish(); +} diff --git a/src/transaction.hpp b/src/transaction.hpp @@ -25,8 +25,10 @@ #include <boost/signals2.hpp> #include <set> +class InstallTask; class Remote; class RemoteIndex; +class RemoveTask; class Task; class Transaction { @@ -51,14 +53,16 @@ public: void onDestroy(const Callback &callback) { m_onDestroy.connect(callback); } void synchronize(const Remote &); + void uninstall(const Remote &); void install(); void cancel(); bool isCancelled() const { return m_isCancelled; } DownloadQueue *downloadQueue() { return &m_queue; } - const PackageEntryList &packages() const { return m_packages; } + size_t taskCount() const { return m_tasks.size(); } const PackageEntryList &newPackages() const { return m_new; } const PackageEntryList &updates() const { return m_updates; } + const std::vector<Path> &removals() const { return m_removals; } const ErrorList &errors() const { return m_errors; } private: @@ -70,6 +74,8 @@ private: }; friend Task; + friend InstallTask; + friend RemoveTask; void updateAll(); void finish(); @@ -80,6 +86,7 @@ private: Path prefixPath(const Path &) const; bool allFilesExists(const std::set<Path> &) const; void registerFiles(const std::set<Path> &); + void addTask(Task *); Registry *m_registry; @@ -93,6 +100,7 @@ private: PackageEntryList m_packages; PackageEntryList m_new; PackageEntryList m_updates; + std::vector<Path> m_removals; ErrorList m_errors; std::vector<Task *> m_tasks; diff --git a/test/path.cpp b/test/path.cpp @@ -150,3 +150,16 @@ TEST_CASE("absolute path (unix)", M) { REQUIRE(a.join() == "/usr/bin/zsh"); } #endif + +TEST_CASE("remove last component of path", M) { + Path a; + a.append("a"); + a.append("b"); + + CHECK(a.size() == 2); + + a.removeLast(); + + REQUIRE(a.size() == 1); + REQUIRE(a[0] == "a"); +} diff --git a/test/registry.cpp b/test/registry.cpp @@ -6,13 +6,14 @@ #include <index.hpp> #include <package.hpp> +#include <remote.hpp> using namespace std; static const char *M = "[registry]"; #define MAKE_PACKAGE \ - RemoteIndex ri("Hello"); \ + RemoteIndex ri("Remote Name"); \ Category cat("Hello", &ri); \ Package pkg(Package::ScriptType, "Hello", &cat); \ Version *ver = new Version("1.0", &pkg); \ @@ -74,3 +75,34 @@ TEST_CASE("get file list", M) { REQUIRE(files == ver->files()); } + +TEST_CASE("query all packages", M) { + MAKE_PACKAGE + + const Remote remote("Remote Name", "irrelevent_url"); + + Registry reg; + REQUIRE(reg.queryAll(remote).empty()); + + reg.push(ver); + + const vector<Registry::Entry> entries = reg.queryAll(remote); + REQUIRE(entries.size() == 1); + REQUIRE(entries[0].id == 1); + REQUIRE(entries[0].status == Registry::Unknown); + REQUIRE(entries[0].version == ver->code()); +} + +TEST_CASE("forget registry entry", M) { + MAKE_PACKAGE + + Registry reg; + reg.push(ver); + + reg.forget(reg.query(&pkg)); + + const Registry::Entry afterForget = reg.query(&pkg); + REQUIRE(afterForget.id == 0); + REQUIRE(afterForget.status == Registry::Uninstalled); + REQUIRE(afterForget.version == 0); +}