From 4fcd32b536f3d29755b1fecc7e3f06be64f996ca Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 24 Feb 2018 18:21:39 +0300 Subject: Add support for rep-list and rep-remove, update rep-add --- bpkg/rep-remove.cxx | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 bpkg/rep-remove.cxx (limited to 'bpkg/rep-remove.cxx') diff --git a/bpkg/rep-remove.cxx b/bpkg/rep-remove.cxx new file mode 100644 index 0000000..d561c0c --- /dev/null +++ b/bpkg/rep-remove.cxx @@ -0,0 +1,288 @@ +// file : bpkg/rep-remove.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include // find() + +#include +#include +#include +#include +#include + +using namespace std; + +namespace bpkg +{ + // Return true if the repository is reachable from the root repository via + // the complements or prerequisites chains, recursively. + // + // Note that we can end up with a repository dependency cycle via + // prerequisites. Thus we need to make sure that the repository was not + // traversed yet. + // + using repositories = set>, + compare_reference_target>; + + static bool + reachable (database& db, + const shared_ptr& r, + repositories& traversed) + { + const string& nm (r->name); + assert (!nm.empty ()); // Can't be the root repository. + + // We will go upstream until reach the root or traverse through all of the + // dependent repositories. + // + if (!traversed.insert (r).second) // We have already been here. + return false; + + for (const auto& rc: db.query ( + query::complement::name == nm)) + { + const shared_ptr& r (rc); + if (r->name.empty () /* Root? */ || reachable (db, r, traversed)) + return true; + } + + for (const auto& rd: db.query ( + query::prerequisite::name == nm)) + { + // Note that the root repository has no prerequisites. + // + if (reachable (db, rd, traversed)) + return true; + } + + return false; + } + + static inline bool + reachable (database& db, const shared_ptr& r) + { + repositories traversed; + return reachable (db, r, traversed); + } + + // Remove a repository if it is not reachable from the root (and thus is not + // required by any user-added repository). + // + static void + rep_remove (const dir_path& c, database& db, const shared_ptr& r) + { + const string& nm (r->name); + assert (!nm.empty ()); // Can't be the root repository. + + if (reachable (db, r)) + return; + + // Remove this repository from locations of the available packages it + // contains. Remove packages that come from only this repository. + // + for (const auto& rp: db.query ( + query::repository::name == nm)) + { + const shared_ptr& p (rp); + vector& ls (p->locations); + + for (auto i (ls.cbegin ()), e (ls.cend ()); i != e; ++i) + { + if (i->repository.object_id () == nm) + { + ls.erase (i); + break; + } + } + + if (ls.empty ()) + db.erase (p); + else + db.update (p); + } + + // Cleanup the repository state if present. + // + // Note that this step is irreversible on failure. If something goes wrong + // we will end up with a state-less fetched repository and the + // configuration will be broken. Though, this in unlikely to happen, so + // we will not bother for now. + // + // An alternative approach would be to collect all such directories and + // then remove them after committing the transaction. Though, we still may + // fail in the middle due to the filesystem error. + // + dir_path d (repository_state (r->location)); + + if (!d.empty ()) + { + dir_path sd (c / repos_dir / d); + + if (exists (sd)) + rm_r (sd); + } + + // Note that it is essential to erase the repository object from the + // database prior to its complements and prerequisites removal as they + // must be un-referenced first. + // + db.erase (r); + + // Remove dangling complements and prerequisites. + // + // Prior to removing a prerequisite/complement we need to make sure it + // still exists, which may not be the case due to the dependency cycle. + // + auto remove = [&c, &db] (const lazy_shared_ptr& rp) + { + shared_ptr r (db.find (rp.object_id ())); + + if (r) + rep_remove (c, db, r); + }; + + for (const lazy_shared_ptr& cr: r->complements) + { + // Remove the complement unless it is the root repository (see + // rep_fetch() for details). + // + if (cr.object_id () != "") + remove (cr); + } + + for (const lazy_weak_ptr& pr: r->prerequisites) + remove (lazy_shared_ptr (pr)); + } + + int + rep_remove (const rep_remove_options& o, cli::scanner& args) + { + tracer trace ("rep_remove"); + + dir_path c (o.directory ()); + l4 ([&]{trace << "configuration: " << c;}); + + // Check that options and arguments are consistent. + // + if (o.all ()) + { + if (args.more ()) + fail << "both --all|-a and repository name specified" << + info << "run 'bpkg help rep-remove' for more information"; + } + else if (!args.more ()) + fail << "repository name or location argument expected" << + info << "run 'bpkg help rep-remove' for more information"; + + // Build the list of repositories the user wants removed. + // + vector> repos; + + database db (open (c, trace)); + transaction t (db.begin ()); + session s; // Repository dependencies can have cycles. + + shared_ptr root (db.load ("")); + repository::complements_type& ua (root->complements); + + if (o.all ()) + { + for (const lazy_shared_ptr& r: ua) + repos.push_back (r); + } + else + { + while (args.more ()) + { + // Try to map the argument to a user-added repository. + // + lazy_shared_ptr r; + string a (args.next ()); + + if (repository_name (a)) + { + lazy_shared_ptr rp (db, a); + + // Note: we report repositories we could not find for both cases + // below. + // + if (ua.find (rp) != ua.end ()) + r = move (rp); + } + else + { + // Note that we can't obtain the canonical name by creating the + // repository location object as that would require the repository + // type, which is potentially impossible to guess at this stage. So + // lets just construct the repository URL and search for it among + // user-added repositories. The linear search should be fine as we + // don't expect too many of them. + // + try + { + repository_url u (a); + + if (u.empty ()) + fail << "empty repository location"; + + for (const lazy_shared_ptr& rp: ua) + { + if (rp.load ()->location.url () == u) + { + r = rp; + break; + } + } + } + catch (const invalid_argument& e) + { + fail << "invalid repository location '" << a << "': " << e; + } + } + + if (r == nullptr) + fail << "repository '" << a << "' does not exist in this " + << "configuration"; + + // Suppress duplicates. + // + if (find (repos.begin (), repos.end (), r) == repos.end ()) + repos.emplace_back (move (r)); + } + } + + // Remove the repository references from the root. + // + // Note that for efficiency we un-reference all the top-level repositories + // before starting to delete them. + // + for (lazy_shared_ptr& r: repos) + ua.erase (r); + + db.update (root); + + // Remove the dangling repositories from the database, recursively. + // + for (lazy_shared_ptr& r: repos) + { + rep_remove (c, db, r.load ()); + + if (verb) + text << "removed " << r.object_id (); + } + + // If we removed all the user-added repositories then no repositories nor + // packages should stay in the database. + // + assert (!ua.empty () || + (db.query_value () == 0 && + db.query_value () == 0)); + + t.commit (); + + return 0; + } +} -- cgit v1.1