diff options
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 Binary files differnew file mode 100644 index 0000000..890e9e2 --- /dev/null +++ b/tests/rep-list/extra/libbar-1.1.0+1.tar.gz 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 Binary files differnew file mode 100644 index 0000000..97e6e32 --- /dev/null +++ b/tests/rep-list/math/libbar-1.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..5e7fa17 --- /dev/null +++ b/tests/rep-list/stable/libfoo-1.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..6cc5890 --- /dev/null +++ b/tests/rep-list/testing/libbar-2.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..6cc5890 --- /dev/null +++ b/tests/rep-remove/alpha/libbar-2.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..890e9e2 --- /dev/null +++ b/tests/rep-remove/extra/libbar-1.1.0+1.tar.gz 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 Binary files differnew file mode 100644 index 0000000..97e6e32 --- /dev/null +++ b/tests/rep-remove/math/libbar-1.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..5e7fa17 --- /dev/null +++ b/tests/rep-remove/stable/libfoo-1.0.0.tar.gz 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 Binary files differnew file mode 100644 index 0000000..6cc5890 --- /dev/null +++ b/tests/rep-remove/testing/libbar-2.0.0.tar.gz 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 +: |