aboutsummaryrefslogtreecommitdiff
path: root/bpkg/rep-remove.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-02-24 18:21:39 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-02-26 17:50:24 +0300
commit4fcd32b536f3d29755b1fecc7e3f06be64f996ca (patch)
tree4aebf6eeb7ac4de316ddc91b92c264f252f86d44 /bpkg/rep-remove.cxx
parent12a5375f25d6a7be5a5741c728a8f9b8168761a4 (diff)
Add support for rep-list and rep-remove, update rep-add
Diffstat (limited to 'bpkg/rep-remove.cxx')
-rw-r--r--bpkg/rep-remove.cxx288
1 files changed, 288 insertions, 0 deletions
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 <bpkg/rep-remove.hxx>
+
+#include <set>
+#include <algorithm> // find()
+
+#include <bpkg/package.hxx>
+#include <bpkg/package-odb.hxx>
+#include <bpkg/database.hxx>
+#include <bpkg/diagnostics.hxx>
+#include <bpkg/manifest-utility.hxx>
+
+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<reference_wrapper<const shared_ptr<repository>>,
+ compare_reference_target>;
+
+ static bool
+ reachable (database& db,
+ const shared_ptr<repository>& 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<repository_complement_dependent> (
+ query<repository_complement_dependent>::complement::name == nm))
+ {
+ const shared_ptr<repository>& r (rc);
+ if (r->name.empty () /* Root? */ || reachable (db, r, traversed))
+ return true;
+ }
+
+ for (const auto& rd: db.query<repository_prerequisite_dependent> (
+ query<repository_prerequisite_dependent>::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<repository>& 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<repository>& 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<repository_package> (
+ query<repository_package>::repository::name == nm))
+ {
+ const shared_ptr<available_package>& p (rp);
+ vector<package_location>& 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<repository>& rp)
+ {
+ shared_ptr<repository> r (db.find<repository> (rp.object_id ()));
+
+ if (r)
+ rep_remove (c, db, r);
+ };
+
+ for (const lazy_shared_ptr<repository>& 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<repository>& pr: r->prerequisites)
+ remove (lazy_shared_ptr<repository> (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<lazy_shared_ptr<repository>> repos;
+
+ database db (open (c, trace));
+ transaction t (db.begin ());
+ session s; // Repository dependencies can have cycles.
+
+ shared_ptr<repository> root (db.load<repository> (""));
+ repository::complements_type& ua (root->complements);
+
+ if (o.all ())
+ {
+ for (const lazy_shared_ptr<repository>& r: ua)
+ repos.push_back (r);
+ }
+ else
+ {
+ while (args.more ())
+ {
+ // Try to map the argument to a user-added repository.
+ //
+ lazy_shared_ptr<repository> r;
+ string a (args.next ());
+
+ if (repository_name (a))
+ {
+ lazy_shared_ptr<repository> 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<repository>& 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<repository>& r: repos)
+ ua.erase (r);
+
+ db.update (root);
+
+ // Remove the dangling repositories from the database, recursively.
+ //
+ for (lazy_shared_ptr<repository>& 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<repository_count> () == 0 &&
+ db.query_value<available_package_count> () == 0));
+
+ t.commit ();
+
+ return 0;
+ }
+}