aboutsummaryrefslogtreecommitdiff
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
parent12a5375f25d6a7be5a5741c728a8f9b8168761a4 (diff)
Add support for rep-list and rep-remove, update rep-add
-rw-r--r--bpkg/bpkg.cxx4
-rw-r--r--bpkg/buildfile8
-rw-r--r--bpkg/manifest-utility.cxx26
-rw-r--r--bpkg/manifest-utility.hxx5
-rw-r--r--bpkg/package.hxx55
-rw-r--r--bpkg/rep-add.cxx62
-rw-r--r--bpkg/rep-info.cxx2
-rw-r--r--bpkg/rep-list.cxx124
-rw-r--r--bpkg/rep-list.hxx19
-rw-r--r--bpkg/rep-remove.cxx288
-rw-r--r--bpkg/rep-remove.hxx19
-rw-r--r--bpkg/types.hxx5
-rw-r--r--tests/common.test2
-rw-r--r--tests/rep-add.test107
-rw-r--r--tests/rep-list.test142
-rw-r--r--tests/rep-list/extra/libbar-1.1.0+1.tar.gzbin0 -> 243 bytes
-rw-r--r--tests/rep-list/extra/repositories3
l---------tests/rep-list/git/libbar.tar1
l---------tests/rep-list/git/style-basic.tar1
-rw-r--r--tests/rep-list/math/libbar-1.0.0.tar.gzbin0 -> 241 bytes
-rw-r--r--tests/rep-list/math/repositories3
-rw-r--r--tests/rep-list/stable/libfoo-1.0.0.tar.gzbin0 -> 240 bytes
-rw-r--r--tests/rep-list/stable/repositories3
-rw-r--r--tests/rep-list/testing/libbar-2.0.0.tar.gzbin0 -> 245 bytes
-rw-r--r--tests/rep-list/testing/repositories6
-rw-r--r--tests/rep-remove.test139
-rw-r--r--tests/rep-remove/alpha/libbar-2.0.0.tar.gzbin0 -> 245 bytes
-rw-r--r--tests/rep-remove/alpha/repositories1
-rw-r--r--tests/rep-remove/extra/libbar-1.1.0+1.tar.gzbin0 -> 243 bytes
-rw-r--r--tests/rep-remove/extra/repositories3
l---------tests/rep-remove/git/style-basic.tar1
-rw-r--r--tests/rep-remove/math/libbar-1.0.0.tar.gzbin0 -> 241 bytes
-rw-r--r--tests/rep-remove/math/repositories3
-rw-r--r--tests/rep-remove/stable/libfoo-1.0.0.tar.gzbin0 -> 240 bytes
-rw-r--r--tests/rep-remove/stable/repositories3
-rw-r--r--tests/rep-remove/testing/libbar-2.0.0.tar.gzbin0 -> 245 bytes
-rw-r--r--tests/rep-remove/testing/repositories6
37 files changed, 981 insertions, 60 deletions
diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx
index 9606b66..457df93 100644
--- a/bpkg/bpkg.cxx
+++ b/bpkg/bpkg.cxx
@@ -43,6 +43,8 @@
#include <bpkg/rep-create.hxx>
#include <bpkg/rep-fetch.hxx>
#include <bpkg/rep-info.hxx>
+#include <bpkg/rep-list.hxx>
+#include <bpkg/rep-remove.hxx>
using namespace std;
using namespace bpkg;
@@ -301,6 +303,8 @@ try
REP_COMMAND (create);
REP_COMMAND (fetch);
REP_COMMAND (info);
+ REP_COMMAND (list);
+ REP_COMMAND (remove);
assert (false);
fail << "unhandled command";
diff --git a/bpkg/buildfile b/bpkg/buildfile
index 9bdf29b..f25e08e 100644
--- a/bpkg/buildfile
+++ b/bpkg/buildfile
@@ -38,7 +38,9 @@ pkg-verify-options \
rep-add-options \
rep-create-options \
rep-fetch-options \
-rep-info-options
+rep-info-options \
+rep-list-options \
+rep-remove-options
help_topics = repository-signing
@@ -96,9 +98,11 @@ if $cli.configured
# rep-* command.
#
cli.cxx{rep-add-options}: cli{rep-add}
+ cli.cxx{rep-create-options}: cli{rep-create}
cli.cxx{rep-fetch-options}: cli{rep-fetch}
cli.cxx{rep-info-options}: cli{rep-info}
- cli.cxx{rep-create-options}: cli{rep-create}
+ cli.cxx{rep-list-options}: cli{rep-list}
+ cli.cxx{rep-remove-options}: cli{rep-remove}
# Help topics.
#
diff --git a/bpkg/manifest-utility.cxx b/bpkg/manifest-utility.cxx
index ed50298..1500cee 100644
--- a/bpkg/manifest-utility.cxx
+++ b/bpkg/manifest-utility.cxx
@@ -142,4 +142,30 @@ namespace bpkg
assert (false); // Can't be here.
return dir_path ();
}
+
+ bool
+ repository_name (const string& s)
+ {
+ size_t n (s.size ());
+ size_t p (s.find (':'));
+
+ // If it has no scheme or starts with the URL scheme (followed by ://) then
+ // this is not a canonical name.
+ //
+ if (p == string::npos || (p + 2 < n && s[p + 1] == '/' && s[p + 2] == '/'))
+ return false;
+
+ // This is a canonical name if the scheme is convertible to the repository
+ // type.
+ //
+ try
+ {
+ to_repository_type (string (s, 0, p));
+ return true;
+ }
+ catch (const invalid_argument&)
+ {
+ return false;
+ }
+ }
}
diff --git a/bpkg/manifest-utility.hxx b/bpkg/manifest-utility.hxx
index f672ba7..f0d5cdd 100644
--- a/bpkg/manifest-utility.hxx
+++ b/bpkg/manifest-utility.hxx
@@ -53,6 +53,11 @@ namespace bpkg
//
dir_path
repository_state (const repository_location&);
+
+ // Return true if the argument is a valid repository canonical name.
+ //
+ bool
+ repository_name (const string&);
}
#endif // BPKG_MANIFEST_UTILITY_HXX
diff --git a/bpkg/package.hxx b/bpkg/package.hxx
index 23fe9b2..54efca1 100644
--- a/bpkg/package.hxx
+++ b/bpkg/package.hxx
@@ -761,6 +761,61 @@ namespace bpkg
optional<dependency_constraint> constraint;
};
+ // Return a list of repositories that depend on this repository as a
+ // complement.
+ //
+ // Note that the last db object pragma is required to produce an object
+ // loading view.
+ //
+ #pragma db view object(repository = complement) \
+ table("repository_complements" = "rc" inner: \
+ "rc.complement = " + complement::name) \
+ object(repository inner: "rc.repository = " + repository::name)
+ struct repository_complement_dependent
+ {
+ shared_ptr<repository> object;
+
+ operator const shared_ptr<repository> () const {return object;}
+ };
+
+ // Return a list of repositories that depend on this repository as a
+ // prerequisite.
+ //
+ // Note that the last db object pragma is required to produce an object
+ // loading view.
+ //
+ #pragma db view object(repository = prerequisite) \
+ table("repository_prerequisites" = "rp" inner: \
+ "rp.prerequisite = " + prerequisite::name) \
+ object(repository inner: "rp.repository = " + repository::name)
+ struct repository_prerequisite_dependent
+ {
+ shared_ptr<repository> object;
+
+ operator const shared_ptr<repository> () const {return object;}
+ };
+
+ // Return a list of packages available from this repository.
+ //
+ #pragma db view object(repository) \
+ table("available_package_locations" = "pl" inner: \
+ "pl.repository = " + repository::name) \
+ object(available_package inner: \
+ "pl.name = " + available_package::id.name + "AND" + \
+ "pl.version_epoch = " + \
+ available_package::id.version.epoch + "AND" + \
+ "pl.version_canonical_upstream = " + \
+ available_package::id.version.canonical_upstream + "AND" + \
+ "pl.version_canonical_release = " + \
+ available_package::id.version.canonical_release + "AND" + \
+ "pl.version_revision = " + \
+ available_package::id.version.revision)
+ struct repository_package
+ {
+ shared_ptr<available_package> object;
+
+ operator const shared_ptr<available_package> () const {return object;}
+ };
// Version comparison operators.
//
diff --git a/bpkg/rep-add.cxx b/bpkg/rep-add.cxx
index 7adb843..690b584 100644
--- a/bpkg/rep-add.cxx
+++ b/bpkg/rep-add.cxx
@@ -27,43 +27,53 @@ namespace bpkg
fail << "repository location argument expected" <<
info << "run 'bpkg help rep-add' for more information";
- repository_location rl (
- parse_location (args.next (),
- o.type_specified ()
- ? optional<repository_type> (o.type ())
- : nullopt));
-
- const string& rn (rl.canonical_name ());
-
- // Create the new repository and add is as a complement to the root.
- //
database db (open (c, trace));
transaction t (db.begin ());
session s; // Repository dependencies can have cycles.
- // It is possible that this repository is already in the database.
- // For example, it might be a prerequisite of one of the already
- // added repository.
- //
- shared_ptr<repository> r (db.find<repository> (rl.canonical_name ()));
+ shared_ptr<repository> root (db.load<repository> (""));
- if (r == nullptr)
+ while (args.more ())
{
- r.reset (new repository (rl));
- db.persist (r);
+ repository_location rl (
+ parse_location (args.next (),
+ o.type_specified ()
+ ? optional<repository_type> (o.type ())
+ : nullopt));
+
+ const string& rn (rl.canonical_name ());
+
+ // Create the new repository if it is not in the database yet. Otherwise
+ // update its location. Add it as a complement to the root repository (if
+ // it is not there yet).
+ //
+ shared_ptr<repository> r (db.find<repository> (rn));
+
+ bool updated (false);
+
+ if (r == nullptr)
+ {
+ r.reset (new repository (rl));
+ db.persist (r);
+ }
+ else if (r->location.url () != rl.url ())
+ {
+ r->location = rl;
+ db.update (r);
+
+ updated = true;
+ }
+
+ bool added (
+ root->complements.insert (lazy_shared_ptr<repository> (db, r)).second);
+
+ if (verb)
+ text << (added ? "added " : updated ? "updated " : "unchanged ") << rn;
}
- shared_ptr<repository> root (db.load<repository> (""));
-
- if (!root->complements.insert (lazy_shared_ptr<repository> (db, r)).second)
- fail << rn << " is already a repository of this configuration";
-
db.update (root);
t.commit ();
- if (verb)
- text << "added repository " << rn;
-
return 0;
}
}
diff --git a/bpkg/rep-info.cxx b/bpkg/rep-info.cxx
index 467c005..0e7f911 100644
--- a/bpkg/rep-info.cxx
+++ b/bpkg/rep-info.cxx
@@ -4,7 +4,7 @@
#include <bpkg/rep-info.hxx>
-#include <iostream> // cout
+#include <iostream> // cout
#include <libbutl/sha256.mxx> // sha256_to_fingerprint()
#include <libbutl/manifest-serializer.mxx>
diff --git a/bpkg/rep-list.cxx b/bpkg/rep-list.cxx
new file mode 100644
index 0000000..f2aca2c
--- /dev/null
+++ b/bpkg/rep-list.cxx
@@ -0,0 +1,124 @@
+// file : bpkg/rep-list.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <bpkg/rep-list.hxx>
+
+#include <set>
+#include <iostream> // cout
+
+#include <bpkg/package.hxx>
+#include <bpkg/package-odb.hxx>
+#include <bpkg/database.hxx>
+#include <bpkg/diagnostics.hxx>
+
+using namespace std;
+
+namespace bpkg
+{
+ // Print the repository dependencies, recursively.
+ //
+ // Each line has the following form:
+ //
+ // [(complement|prerequisite) ]<name> <location>
+ //
+ // and is indented with 2 additional spaces for each recursion level.
+ //
+ // Note that we can end up with a repository dependency cycle via
+ // prerequisites. Thus we need to make sure that the repository is not in
+ // the dependency chain yet.
+ //
+ using repositories = set<reference_wrapper<const shared_ptr<repository>>,
+ compare_reference_target>;
+
+ static void
+ print_dependencies (const rep_list_options& o,
+ const shared_ptr<repository>& r,
+ string& indent,
+ repositories& chain)
+ {
+ assert (!r->name.empty ()); // Can't be the root repository.
+
+ if (!chain.insert (r).second) // Is already in the chain.
+ return;
+
+ indent += " ";
+
+ if (o.complements ())
+ {
+ for (const lazy_shared_ptr<repository>& rp: r->complements)
+ {
+ // Skip the root complement (see rep_fetch() for details).
+ //
+ if (rp.object_id () == "")
+ continue;
+
+ shared_ptr<repository> r (rp.load ());
+
+ cout << indent << "complement "
+ << r->location.canonical_name () << " " << r->location << endl;
+
+ print_dependencies (o, r, indent, chain);
+ }
+ }
+
+ if (o.prerequisites ())
+ {
+ for (const lazy_weak_ptr<repository>& rp: r->prerequisites)
+ {
+ shared_ptr<repository> r (rp.load ());
+
+ cout << indent << "prerequisite "
+ << r->location.canonical_name () << " " << r->location << endl;
+
+ print_dependencies (o, r, indent, chain);
+ }
+ }
+
+ indent.pop_back ();
+ indent.pop_back ();
+
+ chain.erase (r);
+ }
+
+ static inline void
+ print_dependencies (const rep_list_options& o,
+ const shared_ptr<repository>& r)
+ {
+ string indent;
+ repositories chain;
+ print_dependencies (o, r, indent, chain);
+ }
+
+ int
+ rep_list (const rep_list_options& o, cli::scanner& args)
+ {
+ tracer trace ("rep_list");
+
+ dir_path c (o.directory ());
+ l4 ([&]{trace << "configuration: " << c;});
+
+ if (args.more ())
+ fail << "unexpected argument '" << args.next () << "'" <<
+ info << "run 'bpkg help rep-list' for more information";
+
+ database db (open (c, trace));
+ transaction t (db.begin ());
+ session s; // Repository dependencies can have cycles.
+
+ shared_ptr<repository> root (db.load<repository> (""));
+
+ for (const lazy_shared_ptr<repository>& rp: root->complements)
+ {
+ shared_ptr<repository> r (rp.load ());
+ cout << r->location.canonical_name () << " " << r->location << endl;
+
+ if (o.complements () || o.prerequisites ())
+ print_dependencies (o, r);
+ }
+
+ t.commit ();
+
+ return 0;
+ }
+}
diff --git a/bpkg/rep-list.hxx b/bpkg/rep-list.hxx
new file mode 100644
index 0000000..f0d4bab
--- /dev/null
+++ b/bpkg/rep-list.hxx
@@ -0,0 +1,19 @@
+// file : bpkg/rep-list.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_REP_LIST_HXX
+#define BPKG_REP_LIST_HXX
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/rep-list-options.hxx>
+
+namespace bpkg
+{
+ int
+ rep_list (const rep_list_options&, cli::scanner& args);
+}
+
+#endif // BPKG_REP_LIST_HXX
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;
+ }
+}
diff --git a/bpkg/rep-remove.hxx b/bpkg/rep-remove.hxx
new file mode 100644
index 0000000..8bc6144
--- /dev/null
+++ b/bpkg/rep-remove.hxx
@@ -0,0 +1,19 @@
+// file : bpkg/rep-remove.hxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BPKG_REP_REMOVE_HXX
+#define BPKG_REP_REMOVE_HXX
+
+#include <bpkg/types.hxx>
+#include <bpkg/utility.hxx>
+
+#include <bpkg/rep-remove-options.hxx>
+
+namespace bpkg
+{
+ int
+ rep_remove (const rep_remove_options&, cli::scanner& args);
+}
+
+#endif // BPKG_REP_REMOVE_HXX
diff --git a/bpkg/types.hxx b/bpkg/types.hxx
index 82a6ac9..0433e92 100644
--- a/bpkg/types.hxx
+++ b/bpkg/types.hxx
@@ -23,6 +23,7 @@
#include <odb/lazy-ptr.hxx>
#include <libbutl/path.mxx>
+#include <libbutl/utility.mxx> // compare_reference_target
#include <libbutl/optional.mxx>
#include <libbutl/fdstream.mxx>
@@ -64,6 +65,10 @@ namespace bpkg
using std::system_error;
using io_error = std::ios_base::failure;
+ // <libbutl/utility.mxx>
+ //
+ using butl::compare_reference_target;
+
// <libbutl/optional.mxx>
//
using butl::optional;
diff --git a/tests/common.test b/tests/common.test
index b919da4..4002df3 100644
--- a/tests/common.test
+++ b/tests/common.test
@@ -33,6 +33,8 @@ rep_add = $* rep-add
rep_create = $* rep-create
rep_fetch = $* rep-fetch
rep_info = $* rep-info
+rep_list = $* rep-list
+rep_remove = $* rep-remove
# All testscripts are named after bpkg commands, for example pkg-verify.test.
# So the testscript scope id is a name of the command being tested.
diff --git a/tests/rep-add.test b/tests/rep-add.test
index be72b51..c96b32b 100644
--- a/tests/rep-add.test
+++ b/tests/rep-add.test
@@ -4,6 +4,8 @@
.include common.test config.test
+rep_list += -d cfg
+
: location
:
{
@@ -18,9 +20,13 @@
: empty
:
- $* '' 2>>EOE != 0
- error: empty repository location
- EOE
+ {
+ $clone_cfg;
+
+ $* '' 2>>EOE != 0
+ error: empty repository location
+ EOE
+ }
: unknown-type
:
@@ -30,28 +36,43 @@
: no-version
:
- $* 'stable' 2>>/~%EOE% != 0
- %error: invalid bpkg repository location '.+/no-version/stable': missing repository version%
- info: consider using --type to specify repository type
- EOE
+ {
+ $clone_cfg;
+
+ $* 'stable' 2>>/~%EOE% != 0
+ %error: invalid bpkg repository location '.+/no-version/stable': missing repository version%
+ info: consider using --type to specify repository type
+ EOE
+ }
: git-no-branch
:
- $* 'git://example.org/repo' 2>>EOE != 0
- error: invalid git repository location 'git://example.org/repo': missing branch/tag or commit id for git repository
- EOE
+ {
+ $clone_cfg;
+
+ $* 'git://example.org/repo' 2>>EOE != 0
+ error: invalid git repository location 'git://example.org/repo': missing branch/tag or commit id for git repository
+ EOE
+ }
: bpkg-git-scheme
:
- $* 'git://example.org/repo' --type bpkg 2>>EOE != 0
- error: invalid bpkg repository location 'git://example.org/repo': unsupported scheme for bpkg repository
- EOE
+ {
+ $clone_cfg;
+
+ $* 'git://example.org/repo' --type bpkg 2>>EOE != 0
+ error: invalid bpkg repository location 'git://example.org/repo': unsupported scheme for bpkg repository
+ EOE
+ }
: invalid-path
:
{
s="../../../../../../../../../../../../../../../../../../../../../../../"
s="$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s"
+
+ $clone_cfg;
+
$* "$s" 2>>~%EOE% != 0
%error: invalid repository path '.+/': invalid filesystem path%
EOE
@@ -66,21 +87,21 @@
:
$clone_cfg;
$* 'git://example.org/repo#master' 2>>EOE
- added repository git:example.org/repo#master
+ added git:example.org/repo#master
EOE
: http-git
:
$clone_cfg;
$* 'http://example.org/repo.git#master' 2>>EOE
- added repository git:example.org/repo#master
+ added git:example.org/repo#master
EOE
: http-bpkg
:
$clone_cfg;
$* 'http://example.org/1/repo' 2>>EOE
- added repository bpkg:example.org/repo
+ added bpkg:example.org/repo
EOE
: file-git
@@ -96,7 +117,7 @@
$clone_cfg;
$* '1/repo' 2>>/~%EOE%
- %added repository .+/repo%
+ %added .+/repo%
EOE
}
}
@@ -107,11 +128,11 @@
$clone_cfg;
$* ./1/bar/stable 2>>/~%EOE%;
- %added repository bpkg:.+/relative-path/bar/stable%
+ %added bpkg:.+/relative-path/bar/stable%
EOE
- $* ./1/../1/bar/stable 2>>/~%EOE% != 0
- %error: bpkg:.+/relative-path/bar/stable is already a repository of this configuration%
+ $* ./1/../1/bar/stable 2>>/~%EOE%
+ %unchanged bpkg:.+/relative-path/bar/stable%
EOE
}
@@ -121,11 +142,11 @@
$clone_cfg;
$* $~/1/foo/stable 2>>/~%EOE%;
- %added repository bpkg:.+/absolute-path/foo/stable%
+ %added bpkg:.+/absolute-path/foo/stable%
EOE
- $* $~/1/../1/foo/stable 2>>/~%EOE% != 0
- %error: bpkg:.+/absolute-path/foo/stable is already a repository of this configuration%
+ $* $~/1/../1/foo/stable 2>>/~%EOE%
+ %unchanged bpkg:.+/absolute-path/foo/stable%
EOE
}
@@ -138,23 +159,47 @@
:
$clone_cfg;
- $* 'http://pkg.example.org/1/testing' 2>>~%EOE%;
- %added repository bpkg:example.org/testing%
+ $* 'http://pkg.example.org/1/testing' 2>>EOE;
+ added bpkg:example.org/testing
EOE
- $* 'https://www.example.org/1/testing' 2>>~%EOE% != 0
- %error: bpkg:example.org/testing is already a repository of this configuration%
+ $* 'https://www.example.org/1/testing' 2>>EOE;
+ updated bpkg:example.org/testing
EOE
+ $rep_list >>EOO
+ bpkg:example.org/testing https://www.example.org/1/testing
+ EOO
+
: git
:
$clone_cfg;
- $* 'git://example.org/testing#master' 2>>~%EOE%;
- %added repository git:example.org/testing#master%
+ $* 'git://example.org/testing.git#master' 2>>~%EOE%;
+ %added git:example.org/testing#master%
EOE
- $* 'git://www.example.org/testing#master' 2>>~%EOE% != 0
- %error: git:example.org/testing#master is already a repository of this configuration%
+ $* 'https://www.example.org/testing.git#master' 2>>EOE;
+ updated git:example.org/testing#master
EOE
+
+ $rep_list >>EOO
+ git:example.org/testing#master https://www.example.org/testing.git#master
+ EOO
+}
+
+: multiple-locations
+:
+{
+ $clone_cfg;
+
+ $* 'http://pkg.example.org/1/alpha' 'http://pkg.example.org/1/beta' 2>>EOE;
+ added bpkg:example.org/alpha
+ added bpkg:example.org/beta
+ EOE
+
+ $rep_list >>EOO
+ bpkg:example.org/alpha http://pkg.example.org/1/alpha
+ bpkg:example.org/beta http://pkg.example.org/1/beta
+ EOO
}
diff --git a/tests/rep-list.test b/tests/rep-list.test
new file mode 100644
index 0000000..aea7d84
--- /dev/null
+++ b/tests/rep-list.test
@@ -0,0 +1,142 @@
+# file : tests/rep-list.test
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+.include common.test config.test remote.test remote-git.test
+
+# Source repository:
+#
+# rep-list
+# |-- extra -> stable (prerequisite)
+# | |-- libbar-1.1.0+1.tar.gz
+# | `-- repositories
+# |
+# |-- math -> extra (prerequisite)
+# | |-- libbar-1.0.0.tar.gz
+# | `-- repositories
+# |
+# |-- stable -> math (prerequisite)
+# | |-- libfoo-1.0.0.tar.gz
+# | `-- repositories
+# |
+# `-- testing -> stable (complement), extra (prerequisite)
+# | |-- libbar-2.0.0.tar.gz
+# | `-- repositories
+# |
+# `-- git
+# |-- libbar.git -> style-basic.git (prerequisite)
+# `-- style-basic.git
+
+# Prepare repositories used by tests if running in the local mode.
+#
++if ($remote != true)
+ c = $rep_create 2>!
+
+ cp -r $src/extra $out/extra && $c $out/extra &$out/extra/packages
+ cp -r $src/math $out/math && $c $out/math &$out/math/packages
+ cp -r $src/stable $out/stable && $c $out/stable &$out/stable/packages
+ cp -r $src/testing $out/testing && $c $out/testing &$out/testing/packages
+
+ # Create git repositories.
+ #
+ $git_extract $src/git/libbar.tar
+ $git_extract $src/git/style-basic.tar &$out_git/state0/***
+end
+
+rep_add += -d cfg 2>!
+rep_fetch += -d cfg --auth all --trust-yes 2>!
+
+: unexpected-arg
+:
+{
+ $clone_cfg;
+
+ $* unexpected 2>>EOE != 0
+ error: unexpected argument 'unexpected'
+ info: run 'bpkg help rep-list' for more information
+ EOE
+}
+
+: top-level
+:
+{
+ $clone_cfg;
+ $rep_add $rep/stable && $rep_add $rep/testing && $rep_fetch;
+
+ $* >>"EOO"
+ bpkg:build2.org/rep-list/stable ($rep/stable)
+ bpkg:build2.org/rep-list/testing ($rep/testing)
+ EOO
+}
+
+: prerequisites
+:
+: Note that here we also test that the prerequisites cycle is handled properly.
+:
+{
+ $clone_cfg;
+ $rep_add $rep/stable && $rep_fetch;
+
+ $* --prerequisites >>"EOO"
+ bpkg:build2.org/rep-list/stable ($rep/stable)
+ prerequisite bpkg:build2.org/rep-list/math ($rep/math)
+ prerequisite bpkg:build2.org/rep-list/extra ($rep/extra)
+ prerequisite bpkg:build2.org/rep-list/stable ($rep/stable)
+ EOO
+}
+
+: complements
+:
+{
+ $clone_cfg;
+ $rep_add $rep/testing && $rep_fetch;
+
+ $* --complements >>"EOO"
+ bpkg:build2.org/rep-list/testing ($rep/testing)
+ complement bpkg:build2.org/rep-list/stable ($rep/stable)
+ EOO
+}
+
+: all
+:
+{
+ $clone_cfg;
+ $rep_add $rep/testing && $rep_fetch;
+
+ $* --prerequisites --complements >>"EOO"
+ bpkg:build2.org/rep-list/testing ($rep/testing)
+ complement bpkg:build2.org/rep-list/stable ($rep/stable)
+ prerequisite bpkg:build2.org/rep-list/math ($rep/math)
+ prerequisite bpkg:build2.org/rep-list/extra ($rep/extra)
+ prerequisite bpkg:build2.org/rep-list/stable ($rep/stable)
+ prerequisite bpkg:build2.org/rep-list/extra ($rep/extra)
+ prerequisite bpkg:build2.org/rep-list/stable ($rep/stable)
+ prerequisite bpkg:build2.org/rep-list/math ($rep/math)
+ prerequisite bpkg:build2.org/rep-list/extra ($rep/extra)
+ EOO
+}
+
+: git-repos
+:
+if ($git_supported != true)
+{
+ # Skip git repository tests.
+ #
+}
+else
+{
+ rep = "$rep_git/state0"
+ test.cleanups += &cfg/.bpkg/repositories/*/***
+
+ : root-complement
+ :
+ : Test that the root repository complement is handled properly.
+ :
+ $clone_root_cfg;
+ $rep_add "$rep/libbar.git#master" && $rep_fetch;
+
+ $* --complements --prerequisites >>~%EOO%
+ %git:.+libbar#master .+libbar\.git#master%
+ % prerequisite git:.+style-basic#stable .+style-basic\.git#stable%
+ EOO
+}
diff --git a/tests/rep-list/extra/libbar-1.1.0+1.tar.gz b/tests/rep-list/extra/libbar-1.1.0+1.tar.gz
new file mode 100644
index 0000000..890e9e2
--- /dev/null
+++ b/tests/rep-list/extra/libbar-1.1.0+1.tar.gz
Binary files differ
diff --git a/tests/rep-list/extra/repositories b/tests/rep-list/extra/repositories
new file mode 100644
index 0000000..ecaa454
--- /dev/null
+++ b/tests/rep-list/extra/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../stable
+:
diff --git a/tests/rep-list/git/libbar.tar b/tests/rep-list/git/libbar.tar
new file mode 120000
index 0000000..67ccdb1
--- /dev/null
+++ b/tests/rep-list/git/libbar.tar
@@ -0,0 +1 @@
+../../common/git/state0/libbar.tar \ No newline at end of file
diff --git a/tests/rep-list/git/style-basic.tar b/tests/rep-list/git/style-basic.tar
new file mode 120000
index 0000000..2833f83
--- /dev/null
+++ b/tests/rep-list/git/style-basic.tar
@@ -0,0 +1 @@
+../../common/git/state0/style-basic.tar \ No newline at end of file
diff --git a/tests/rep-list/math/libbar-1.0.0.tar.gz b/tests/rep-list/math/libbar-1.0.0.tar.gz
new file mode 100644
index 0000000..97e6e32
--- /dev/null
+++ b/tests/rep-list/math/libbar-1.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-list/math/repositories b/tests/rep-list/math/repositories
new file mode 100644
index 0000000..14d6ce0
--- /dev/null
+++ b/tests/rep-list/math/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../extra
+:
diff --git a/tests/rep-list/stable/libfoo-1.0.0.tar.gz b/tests/rep-list/stable/libfoo-1.0.0.tar.gz
new file mode 100644
index 0000000..5e7fa17
--- /dev/null
+++ b/tests/rep-list/stable/libfoo-1.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-list/stable/repositories b/tests/rep-list/stable/repositories
new file mode 100644
index 0000000..b49d922
--- /dev/null
+++ b/tests/rep-list/stable/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../math
+:
diff --git a/tests/rep-list/testing/libbar-2.0.0.tar.gz b/tests/rep-list/testing/libbar-2.0.0.tar.gz
new file mode 100644
index 0000000..6cc5890
--- /dev/null
+++ b/tests/rep-list/testing/libbar-2.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-list/testing/repositories b/tests/rep-list/testing/repositories
new file mode 100644
index 0000000..7bd7269
--- /dev/null
+++ b/tests/rep-list/testing/repositories
@@ -0,0 +1,6 @@
+: 1
+location: ../stable
+role: complement
+:
+location: ../extra
+:
diff --git a/tests/rep-remove.test b/tests/rep-remove.test
new file mode 100644
index 0000000..0dea240
--- /dev/null
+++ b/tests/rep-remove.test
@@ -0,0 +1,139 @@
+# file : tests/rep-remove.test
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+.include common.test config.test remote.test remote-git.test
+
+# Source repository:
+#
+# rep-remove
+# |-- extra -> stable (prerequisite)
+# | |-- libbar-1.1.0+1.tar.gz
+# | `-- repositories
+# |
+# |-- math -> extra (prerequisite)
+# | |-- libbar-1.0.0.tar.gz
+# | `-- repositories
+# |
+# |-- stable -> math (prerequisite)
+# | |-- libfoo-1.0.0.tar.gz
+# | `-- repositories
+# |
+# `-- testing -> stable (complement), extra (prerequisite)
+# | |-- libbar-2.0.0.tar.gz
+# | `-- repositories
+# |
+# `-- alpha
+# | |-- libbar-2.0.0.tar.gz
+# | `-- repositories
+# |
+# `-- git
+# `-- style-basic.git
+
+# Prepare repositories used by tests if running in the local mode.
+#
++if ($remote != true)
+ c = $rep_create 2>!
+
+ cp -r $src/extra $out/extra && $c $out/extra &$out/extra/packages
+ cp -r $src/math $out/math && $c $out/math &$out/math/packages
+ cp -r $src/stable $out/stable && $c $out/stable &$out/stable/packages
+ cp -r $src/testing $out/testing && $c $out/testing &$out/testing/packages
+ cp -r $src/alpha $out/alpha && $c $out/alpha &$out/alpha/packages
+
+ # Create git repositories.
+ #
+ $git_extract $src/git/style-basic.tar &$out_git/state0/***
+end
+
+rep_add += -d cfg 2>!
+rep_fetch += -d cfg --auth all --trust-yes 2>!
+rep_list += -d cfg
+pkg_status += -d cfg
+
+: by-name
+:
+{
+ $clone_cfg;
+ $rep_add $rep/extra && $rep_fetch;
+
+ $* 'bpkg:build2.org/rep-remove/extra' 2>>"EOE";
+ removed bpkg:build2.org/rep-remove/extra
+ EOE
+
+ $rep_list >:""
+}
+
+: prerequisites-cycle
+:
+{
+ $clone_cfg;
+ $rep_add $rep/testing && $rep_fetch;
+
+ $* $rep/testing 2>>"EOE";
+ removed bpkg:build2.org/rep-remove/testing
+ EOE
+
+ $rep_list >:"";
+ $pkg_status libbar >'unknown';
+ $pkg_status libfoo >'unknown'
+}
+
+: reacheable
+:
+{
+ $clone_cfg;
+ $rep_add $rep/testing && $rep_add $rep/math && $rep_fetch;
+
+ $* $rep/testing 2>>"EOE";
+ removed bpkg:build2.org/rep-remove/testing
+ EOE
+
+ $rep_list --prerequisites --complements >>"EOO";
+ bpkg:build2.org/rep-remove/math ($rep/math)
+ prerequisite bpkg:build2.org/rep-remove/extra ($rep/extra)
+ prerequisite bpkg:build2.org/rep-remove/stable ($rep/stable)
+ prerequisite bpkg:build2.org/rep-remove/math ($rep/math)
+ EOO
+
+ $pkg_status libbar >'available 1.0.0 sys:?';
+ $pkg_status libfoo >'unknown'
+}
+
+: package-locations
+:
+{
+ $clone_cfg;
+ $rep_add $rep/testing && $rep_add $rep/alpha && $rep_fetch;
+
+ $* $rep/testing 2>!;
+ $pkg_status libbar >'available 2.0.0 sys:?';
+
+ $* $rep/alpha 2>!;
+ $pkg_status libbar >'unknown'
+}
+
+: git-repos
+:
+if ($git_supported != true)
+{
+ # Skip git repository tests.
+ #
+}
+else
+{
+ rep = "$rep_git/state0"
+
+ : root-complement
+ :
+ : Test that git repository root complement is handled properly. Note that
+ : we also test that the repository state directory is removed. Otherwise
+ : the testscript would fail to cleanup the working directory.
+ :
+ $clone_root_cfg;
+ $rep_add "$rep/style-basic.git#master" && $rep_fetch;
+
+ $* "$rep/style-basic.git#master" 2>>~%EOO%
+ %removed git:.+style-basic#master%
+ EOO
+}
diff --git a/tests/rep-remove/alpha/libbar-2.0.0.tar.gz b/tests/rep-remove/alpha/libbar-2.0.0.tar.gz
new file mode 100644
index 0000000..6cc5890
--- /dev/null
+++ b/tests/rep-remove/alpha/libbar-2.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-remove/alpha/repositories b/tests/rep-remove/alpha/repositories
new file mode 100644
index 0000000..5b70556
--- /dev/null
+++ b/tests/rep-remove/alpha/repositories
@@ -0,0 +1 @@
+: 1
diff --git a/tests/rep-remove/extra/libbar-1.1.0+1.tar.gz b/tests/rep-remove/extra/libbar-1.1.0+1.tar.gz
new file mode 100644
index 0000000..890e9e2
--- /dev/null
+++ b/tests/rep-remove/extra/libbar-1.1.0+1.tar.gz
Binary files differ
diff --git a/tests/rep-remove/extra/repositories b/tests/rep-remove/extra/repositories
new file mode 100644
index 0000000..ecaa454
--- /dev/null
+++ b/tests/rep-remove/extra/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../stable
+:
diff --git a/tests/rep-remove/git/style-basic.tar b/tests/rep-remove/git/style-basic.tar
new file mode 120000
index 0000000..2833f83
--- /dev/null
+++ b/tests/rep-remove/git/style-basic.tar
@@ -0,0 +1 @@
+../../common/git/state0/style-basic.tar \ No newline at end of file
diff --git a/tests/rep-remove/math/libbar-1.0.0.tar.gz b/tests/rep-remove/math/libbar-1.0.0.tar.gz
new file mode 100644
index 0000000..97e6e32
--- /dev/null
+++ b/tests/rep-remove/math/libbar-1.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-remove/math/repositories b/tests/rep-remove/math/repositories
new file mode 100644
index 0000000..14d6ce0
--- /dev/null
+++ b/tests/rep-remove/math/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../extra
+:
diff --git a/tests/rep-remove/stable/libfoo-1.0.0.tar.gz b/tests/rep-remove/stable/libfoo-1.0.0.tar.gz
new file mode 100644
index 0000000..5e7fa17
--- /dev/null
+++ b/tests/rep-remove/stable/libfoo-1.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-remove/stable/repositories b/tests/rep-remove/stable/repositories
new file mode 100644
index 0000000..b49d922
--- /dev/null
+++ b/tests/rep-remove/stable/repositories
@@ -0,0 +1,3 @@
+: 1
+location: ../math
+:
diff --git a/tests/rep-remove/testing/libbar-2.0.0.tar.gz b/tests/rep-remove/testing/libbar-2.0.0.tar.gz
new file mode 100644
index 0000000..6cc5890
--- /dev/null
+++ b/tests/rep-remove/testing/libbar-2.0.0.tar.gz
Binary files differ
diff --git a/tests/rep-remove/testing/repositories b/tests/rep-remove/testing/repositories
new file mode 100644
index 0000000..7bd7269
--- /dev/null
+++ b/tests/rep-remove/testing/repositories
@@ -0,0 +1,6 @@
+: 1
+location: ../stable
+role: complement
+:
+location: ../extra
+: