From 03c40ed68ce10b26a5f9f509e914b1b54f060215 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 4 Sep 2021 15:41:41 +0300 Subject: Add --backlink, --dangling, and --recursive options to cfg-info --- bpkg/cfg-info.cli | 33 +++++++-- bpkg/cfg-info.cxx | 101 +++++++++++++++++++++---- bpkg/cfg-link.cli | 2 +- bpkg/cfg-link.cxx | 2 +- bpkg/cfg-link.hxx | 2 +- bpkg/cfg-unlink.cli | 4 +- bpkg/cfg-unlink.cxx | 12 +-- bpkg/database.cxx | 33 ++++++--- bpkg/database.hxx | 7 ++ bpkg/package.hxx | 2 +- tests/cfg-info.testscript | 176 ++++++++++++++++++++++++++++++++++++++++++++ tests/cfg-link.testscript | 36 ++++++--- tests/cfg-unlink.testscript | 6 +- 13 files changed, 356 insertions(+), 60 deletions(-) create mode 100644 tests/cfg-info.testscript diff --git a/bpkg/cfg-info.cli b/bpkg/cfg-info.cli index 8002abd..8801ec9 100644 --- a/bpkg/cfg-info.cli +++ b/bpkg/cfg-info.cli @@ -19,14 +19,22 @@ namespace bpkg \h|DESCRIPTION| The \cb{cfg-info} command prints the current configuration's absolute - path, id, type, and name. If the \cb{--link} option is specified then - this information is also printed for each linked configuration, if any. + path, id, type, and name. If the \cb{--link} and/or \cb{--backlink} + options are specified, then this information is also printed for each + linked and/or implicitly backlinked configuration, if any. Note that the + dangling implicit backlinks are silently skipped, unless \cb{--dangling} + is specified, in which case this information is also printed for them. Note that the information is written to \cb{stdout}, not \cb{stderr}. + If the \cb{--recursive} option is specified together with \cb{--link} + and/or \cb{--backlink}, then this information is printed for linked + and/or implicitly backlinked configuration, recursively. + The output format is regular with each value printed on a separate line - and prefixed with the value name. If the \cb{--link} option is specified - then information blocks corresponding to configurations are separated - with blank lines. For example: + and prefixed with the value name. If the \cb{--link}, \cb{--backlink}, + and/or \cb{--dangling} options are specified, then information blocks + corresponding to linked configurations are separated with blank + lines. For example: \ path: /path/to/cfg/ @@ -55,6 +63,21 @@ namespace bpkg { "Print linked configurations." } + + bool --backlink + { + "Print implicitly backlinked configurations." + } + + bool --dangling + { + "Print dangling implicit backlinks." + } + + bool --recursive + { + "Print linked configurations recursively." + } }; " diff --git a/bpkg/cfg-info.cxx b/bpkg/cfg-info.cxx index da49d62..fc65b7b 100644 --- a/bpkg/cfg-info.cxx +++ b/bpkg/cfg-info.cxx @@ -3,8 +3,11 @@ #include +#include #include // cout +#include +#include #include #include @@ -20,35 +23,101 @@ namespace bpkg dir_path c (o.directory ()); l4 ([&]{trace << "configuration: " << c;}); - database db (c, trace, o.link ()); + if (o.recursive () && !o.link () && !o.backlink ()) + fail << "--recursive requires --link or --backlink"; try { cout.exceptions (ostream::badbit | ostream::failbit); - auto print = [] (const database& db) + // Return false if the configuration information has already been + // printed and print the information and return true otherwise. + // + auto print = [first = true, + printed = set {}] + (const dir_path& path, + const uuid& uid, + const string& type, + const optional& name) mutable { - cout << "path: " << db.config << endl - << "uuid: " << db.uuid << endl - << "type: " << db.type << endl - << "name: " << (db.name ? *db.name : "") << endl; + if (!printed.insert (path).second) + return false; + + if (!first) + cout << endl; + else + first = false; + + cout << "path: " << path << endl + << "uuid: " << uid << endl + << "type: " << type << endl + << "name: " << (name ? *name : "") << endl; + + return true; }; - print (db); + using query = odb::query; - // Note that there will be no explicit links loaded, unless the --link - // option is specified. + query q (false); + + if (o.link ()) + q = q || query::expl; + + if (o.backlink () || o.dangling ()) + q = q || (!query::expl && query::id != 0); + + // Make the output consistent across runs. // - for (const linked_config& lc: db.explicit_links ()) + q = q + "ORDER BY" + query::id; + + auto print_db = [&o, &q, &print] (database& db, + bool links, + const auto& print_db) { - // Skip the self-link. - // - if (lc.id != 0) + if (!print (db.config, db.uuid, db.type, db.name)) + return; + + if (links) { - cout << endl; - print (lc.db); + for (auto& c: db.query (q)) + { + const dir_path& d (c.make_effective_path (db.config)); + + auto print_link = [&o, &db, &c, &print_db] () + { + database& ldb (db.attach (c.path)); + db.verify_link (c, ldb); + + // While at it, also verify the backlink. + // + if (c.expl) + db.backlink (ldb); + + print_db (ldb, o.recursive (), print_db); + }; + + if (c.expl) + { + if (o.link ()) + print_link (); + } + else if (exists (d)) + { + if (o.backlink ()) + print_link (); + } + else if (o.dangling ()) + print (d, c.uuid, c.type, c.name); + } } - } + }; + + database db (c, trace, false /* pre_attach */); + transaction t (db); + + print_db (db, o.link () || o.backlink () || o.dangling (), print_db); + + t.commit (); } catch (const io_error&) { diff --git a/bpkg/cfg-link.cli b/bpkg/cfg-link.cli index 562d4c1..906a4d5 100644 --- a/bpkg/cfg-link.cli +++ b/bpkg/cfg-link.cli @@ -20,7 +20,7 @@ namespace bpkg The \cb{cfg-link} command links the specified \cb{bpkg} configuration with the current configuration. Note that it also establishes an implicit - back-link from the specified to the current configuration. See + backlink from the specified to the current configuration. See \l{bpkg-cfg-create(1)} for background on linked configurations. To unlink previously linked configurations use \l{bpkg-cfg-unlink(1)}. diff --git a/bpkg/cfg-link.cxx b/bpkg/cfg-link.cxx index 5cbeef7..1146d22 100644 --- a/bpkg/cfg-link.cxx +++ b/bpkg/cfg-link.cxx @@ -139,7 +139,7 @@ namespace bpkg // if (lcf != nullptr) { - // Verify the back-link integrity. + // Verify the backlink integrity. // shared_ptr cf ( ldb.query_one (query::uuid == db.uuid.string ())); diff --git a/bpkg/cfg-link.hxx b/bpkg/cfg-link.hxx index 116512b..ee625fa 100644 --- a/bpkg/cfg-link.hxx +++ b/bpkg/cfg-link.hxx @@ -17,7 +17,7 @@ namespace bpkg // Link the configuration specified as the directory path with the current // configuration, attach the linked configuration database, and return the - // link. Note that it also establishes an implicit back-link of the current + // link. Note that it also establishes an implicit backlink of the current // configuration with the linked one. // // The specified configuration path must be absolute and normalized. If the diff --git a/bpkg/cfg-unlink.cli b/bpkg/cfg-unlink.cli index ade3373..6514882 100644 --- a/bpkg/cfg-unlink.cli +++ b/bpkg/cfg-unlink.cli @@ -21,7 +21,7 @@ namespace bpkg The \cb{cfg-unlink} command unlinks the specified \cb{bpkg} configuration from the current configuration (the first form) or removes dangling - implicit back-links (the second form). See \l{bpkg-cfg-create(1)} for + implicit backlinks (the second form). See \l{bpkg-cfg-create(1)} for background on linked configurations. In the first form the configuration to unlink can be specified either as @@ -54,7 +54,7 @@ namespace bpkg bool --dangling { - "Remove dangling implicit back-links." + "Remove dangling implicit backlinks." } }; diff --git a/bpkg/cfg-unlink.cxx b/bpkg/cfg-unlink.cxx index e29d949..52d8969 100644 --- a/bpkg/cfg-unlink.cxx +++ b/bpkg/cfg-unlink.cxx @@ -69,7 +69,7 @@ namespace bpkg // Unlink the configuration in the in-memory model, so we can evaluate // if the dependent configurations are still linked with it. // - // Note that we don't remove the back-link here, since this is not + // Note that we don't remove the backlink here, since this is not // required for the check. // if (!priv) @@ -162,7 +162,7 @@ namespace bpkg // assert (uc != nullptr); - // Implicit back-link. + // Implicit backlink. // shared_ptr cc ( udb.query_one (query::uuid == mdb.uuid.string ())); @@ -172,7 +172,7 @@ namespace bpkg // assert (cc != nullptr); - // If the back-link turns out to be explicit, then, unless the + // If the backlink turns out to be explicit, then, unless the // configuration being unlinked is private, we just turn the explicit // link into an implicit one rather then remove the direct and back // links. @@ -181,7 +181,7 @@ namespace bpkg { info << "configurations " << udb.config_orig << " and " << mdb.config_orig << " are mutually linked, turning the link " - << "to " << udb.config_orig << " into implicit back-link"; + << "to " << udb.config_orig << " into implicit backlink"; uc->expl = false; mdb.update (uc); @@ -237,7 +237,7 @@ namespace bpkg if (!exists (c.effective_path (db.config))) { if (verb > 1) - text << "removing dangling implicit back-link " << c.path; + text << "removing dangling implicit backlink " << c.path; db.erase (c); ++count; @@ -247,7 +247,7 @@ namespace bpkg t.commit (); if (verb && !o.no_result ()) - text << "removed " << count << " dangling implicit back-link(s)"; + text << "removed " << count << " dangling implicit backlink(s)"; return 0; } diff --git a/bpkg/database.cxx b/bpkg/database.cxx index ab2bcfc..451ade3 100644 --- a/bpkg/database.cxx +++ b/bpkg/database.cxx @@ -697,7 +697,7 @@ namespace bpkg if (!lc.expl && !exists (d)) { if (verb > 1) - info << "skipping dangling implicit back-link " << lc.path << + info << "skipping dangling implicit backlink " << lc.path << info << "use 'cfg-unlink --dangling' to clean up"; continue; @@ -714,17 +714,7 @@ namespace bpkg // if (lc.expl) { - shared_ptr cf ( - db.query_one (q::uuid == uuid.string ())); - - if (cf == nullptr) - fail << "configuration " << db.config_orig << " is linked with " - << config_orig << " but latter is not implicitly linked " - << "with former"; - - // While at it, verify the integrity of the other end of the link. - // - db.verify_link (*cf, *this); + shared_ptr cf (backlink (db)); if (!cf->expl) continue; @@ -746,6 +736,25 @@ namespace bpkg return implicit_links_; } + shared_ptr database:: + backlink (database& db) + { + using q = odb::query; + + shared_ptr cf ( + db.query_one (q::uuid == uuid.string ())); + + if (cf == nullptr) + fail << "configuration " << db.config_orig << " is linked with " + << config_orig << " but latter is not implicitly linked " + << "with former"; + + // While at it, verify the integrity of the other end of the link. + // + db.verify_link (*cf, *this); + return cf; + } + linked_databases database:: dependent_configs (bool sys_rep) { diff --git a/bpkg/database.hxx b/bpkg/database.hxx index 40474d3..889f8a0 100644 --- a/bpkg/database.hxx +++ b/bpkg/database.hxx @@ -376,6 +376,13 @@ namespace bpkg void verify_link (const configuration&, database&); + // Assuming that the passed configuration is explicitly linked to the + // current one, return the corresponding backlink. Issue diagnostics and + // fail if the backlink is not found. + // + shared_ptr + backlink (database&); + // Set the specified tracer for the whole linked databases cluster. // using tracer_type = odb::tracer; diff --git a/bpkg/package.hxx b/bpkg/package.hxx index 6256481..13efcd4 100644 --- a/bpkg/package.hxx +++ b/bpkg/package.hxx @@ -160,7 +160,7 @@ namespace bpkg dir_path path; // Empty for the self-link. // True if the link is created explicitly by the user rather than - // automatically as a back-link. + // automatically as a backlink. // bool expl; diff --git a/tests/cfg-info.testscript b/tests/cfg-info.testscript new file mode 100644 index 0000000..10e1e4c --- /dev/null +++ b/tests/cfg-info.testscript @@ -0,0 +1,176 @@ +# file : tests/cfg-info.testscript +# license : MIT; see accompanying LICENSE file + +.include common.testscript + +cfg_create += 2>! + +uuid1 = '18f48b4b-b5d9-4712-b98c-1930df1c4228' +uuid2 = '28f48b4b-b5d9-4712-b98c-1930df1c4228' +uuid3 = '38f48b4b-b5d9-4712-b98c-1930df1c4228' +uuid4 = '48f48b4b-b5d9-4712-b98c-1930df1c4228' + ++$cfg_create -d cfg1 --name 't1' --uuid "$uuid1" &cfg1/*** ++$cfg_create -d cfg2 --name 't2' --uuid "$uuid2" &cfg2/*** ++$cfg_create -d cfg3 --name 'h3' --uuid "$uuid3" --type host &cfg3/*** ++$cfg_create -d cfg4 --name 'b4' --uuid "$uuid4" --type build2 &cfg4/*** + ++$cfg_link -d cfg1 cfg3 2>! ++$cfg_link -d cfg2 cfg3 2>! ++$cfg_link -d cfg3 cfg4 2>! + +clone_cfgs = cp -r ../cfg1 ../cfg2 ../cfg3 ../cfg4 ./ + +sp = ' ' + +: self +: +{ + $clone_cfgs; + + $* -d cfg1 >>/"EOO" + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + EOO +} + +: links +: +{ + $clone_cfgs; + + $* -d cfg1 --link >>/"EOO" + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + EOO +} + +: links-recursive +: +{ + $clone_cfgs; + + $* -d cfg1 --link --recursive >>/"EOO" + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg4/ + uuid: $uuid4 + type: build2 + name: b4 + EOO +} + +: backlinks +: +{ + $clone_cfgs; + + $* -d cfg3 --backlink >>/"EOO"; + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + + path: $~/cfg2/ + uuid: $uuid2 + type: target + name: t2 + EOO + + mv cfg2 cfg2.tmp; + + # Make sure that dangling links are silently skipped. + # + $* -d cfg3 --backlink >>/"EOO"; + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + EOO + + # While at it, test printing dangling links. + # + $* -d cfg3 --dangling >>/"EOO"; + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg2/ + uuid: $uuid2 + type: target + name:$sp + EOO + + $* -d cfg3 --dangling --backlink >>/"EOO" + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + + path: $~/cfg2/ + uuid: $uuid2 + type: target + name:$sp + EOO +} + +: all-links-recursive +: +{ + $clone_cfgs; + + $* -d cfg1 --link --backlink --recursive >>/"EOO" + path: $~/cfg1/ + uuid: $uuid1 + type: target + name: t1 + + path: $~/cfg3/ + uuid: $uuid3 + type: host + name: h3 + + path: $~/cfg2/ + uuid: $uuid2 + type: target + name: t2 + + path: $~/cfg4/ + uuid: $uuid4 + type: build2 + name: b4 + EOO +} diff --git a/tests/cfg-link.testscript b/tests/cfg-link.testscript index 26496eb..091d2f0 100644 --- a/tests/cfg-link.testscript +++ b/tests/cfg-link.testscript @@ -17,7 +17,7 @@ acfg_uuid = '28f48b4b-b5d9-4712-b98c-1930df1c4228' $cfg_create -d cfg --name 'main'; $cfg_create -d acfg --name 'shared' --uuid "$acfg_uuid" &acfg/***; - # Try to link configuration under the same name. + # Try to link self configuration. # $* cfg 2>>/~"%EOE%" != 0; %error: linking configuration .+/cfg/ with itself% @@ -53,8 +53,11 @@ acfg_uuid = '28f48b4b-b5d9-4712-b98c-1930df1c4228' name: shared EOO - $pkg_status -d cfg libfoo >'libfoo unknown'; - $pkg_status -d acfg libfoo >'libfoo unknown'; + $cfg_info -d cfg2 --backlink >>/"EOO"; + path: $~/cfg2/ + uuid: $uuid2 + type: target + name: shared # Test that the recreated configuration can be implicitly re-linked. # @@ -76,9 +79,17 @@ acfg_uuid = '28f48b4b-b5d9-4712-b98c-1930df1c4228' %error: configuration with uuid .{36} is already linked as \.\./acfg/% EOE - rm -r acfg; + # Test that the integrity check fails. + # + mv acfg acfg.tmp; - $cfg_create -d acfg --name 'shared' &acfg/***; + $cfg_create -d acfg --name 'shared'; + + $pkg_status -d cfg libfoo 2>>/~"%EOE%" != 0; + error: configuration acfg/ uuid mismatch + % info: uuid .+% + info: linked with cfg/ as $acfg_uuid + EOE # Test that the path clash is reported. # @@ -95,6 +106,8 @@ acfg_uuid = '28f48b4b-b5d9-4712-b98c-1930df1c4228' info: consider specifying alternative name with --name EOE + # Link the second configuration. + # $* acfg2 2>>/~%EOE%; warning: configuration with name shared is already linked as ../acfg/, linking as unnamed %linked with configuration .+/acfg2/% @@ -103,18 +116,17 @@ acfg_uuid = '28f48b4b-b5d9-4712-b98c-1930df1c4228' id: 2 EOE - # Test that the integrity check fails. + # Restore the integrity. # - $pkg_status -d cfg libfoo 2>>/~"%EOE%" != 0; + + $cfg_info -d cfg >- 2>>/~%EOE% != 0; error: configuration acfg/ uuid mismatch - % info: uuid .+% - info: linked with cfg/ as $acfg_uuid + % info: uuid .{36}% + % info: linked with cfg/ as .{36}% EOE - # Link the second configuration. - # rm -r acfg; - $cfg_create -d acfg --name 'shared' --uuid "$acfg_uuid" &acfg/***; + mv acfg.tmp acfg; $cfg_info -d cfg >>/~"%EOO%"; path: $~/cfg/ diff --git a/tests/cfg-unlink.testscript b/tests/cfg-unlink.testscript index b65f4b1..2ca3783 100644 --- a/tests/cfg-unlink.testscript +++ b/tests/cfg-unlink.testscript @@ -148,7 +148,7 @@ clone_root_cfgs = cp -r $~/cfg1 $~/cfg2 ./ $cfg_link -d cfg2 cfg1 2>!; $* -d cfg1 cfg2 2>>/"EOE"; - info: configurations cfg2/ and cfg1/ are mutually linked, turning the link to cfg2/ into implicit back-link + info: configurations cfg2/ and cfg1/ are mutually linked, turning the link to cfg2/ into implicit backlink unlinked configuration $~/cfg2/ EOE @@ -261,8 +261,8 @@ clone_root_cfgs = cp -r $~/cfg1 $~/cfg2 ./ mv cfg1 cfg3; - $* -d cfg2 --dangling 2>'removed 1 dangling implicit back-link(s)'; - $* -d cfg2 --dangling 2>'removed 0 dangling implicit back-link(s)' + $* -d cfg2 --dangling 2>'removed 1 dangling implicit backlink(s)'; + $* -d cfg2 --dangling 2>'removed 0 dangling implicit backlink(s)' } : error -- cgit v1.1