// file : bpkg/cfg-link.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include #include #include #include using namespace std; namespace bpkg { shared_ptr cfg_link (database& db, const dir_path& ld, bool rel, optional name, bool sys_rep) { tracer trace ("cfg_link"); bool name_specified (name); const dir_path& cd (db.config); // Note: absolute and normalized. // Load the self-link object from the database of the configuration being // linked to obtain its name, type, and uuid. // database& ldb (db.attach (ld, sys_rep)); string type; uuid uid; { shared_ptr cf (ldb.load (0)); type = move (cf->type); uid = cf->uuid; if (!name) name = move (cf->name); } if (db.uuid == uid) fail << "linking configuration " << ld << " with itself" << info << "uuid: " << uid; if (name && name == db.name) fail << "linking configuration " << ld << " using current " << "configuration name '" << *name << "'" << info << "consider specifying alternative name with --name"; // Verify that the name and path of the configuration being linked do not // clash with already linked configurations. Fail if configurations with // this uuid is already linked unless the link is implicit, in which case // make it explicit and update its name and path. // // Note that when we make an implicit link explicit, we start treating it // as an implicit and explicit simultaneously. So, for example, for cfg1 // the link cfg2 is explicit and the link cfg3 is both explicit and // implicit: // // cfg2 <- cfg1 <-> cfg3 // // Similar, if we link cfg1 with cfg2, the explicit link cfg2 in cfg1 also // becomes both explicit and implicit, not being amended directly. // shared_ptr lcf; using query = query; for (shared_ptr lc: pointer_result (db.query (query::id != 0))) { if (uid == lc->uuid) { if (lc->expl) fail << "configuration with uuid " << uid << " is already linked " << "as " << lc->path; // Verify the existing implicit link integrity and cache it to update // later, when the name/path clash check is complete. // db.verify_link (*lc, ldb); lcf = move (lc); continue; } if (ld == lc->effective_path (cd)) fail << "configuration with path " << ld << " is already linked"; // If the name clashes, then fail if it was specified by the user and // issue a warning and link the configuration as unnamed otherwise. // if (name && name == lc->name) { diag_record dr (name_specified ? error : warn); dr << "configuration with name " << *name << " is already linked as " << lc->path; if (name_specified) { dr << info << "consider specifying alternative name with --name" << endf; } else { dr << ", linking as unnamed"; name = nullopt; } } } // If requested, rebase the first path relative to the second or return it // as is otherwise. Fail if the rebase is not possible (e.g., paths are on // different drives on Windows). // auto rebase = [rel] (const dir_path& x, const dir_path& y) -> dir_path { try { return rel ? x.relative (y) : x; } catch (const invalid_path&) { fail << "unable to rebase " << x << " relative to " << y << info << "specify absolute configuration directory path to save it " << "as absolute" << endf; } }; // If the implicit link already exists, then make it explicit and update // its name and path. Otherwise, create a new link. // // Note that in the former case the current configuration must already be // explicitly linked with the configuration being linked. We verify that // and the link integrity. // if (lcf != nullptr) { // Verify the backlink integrity. // shared_ptr cf ( ldb.query_one (query::uuid == db.uuid.string ())); // Note: both sides of the link cannot be implicit. // if (cf == nullptr || !cf->expl) fail << "configuration " << ld << " is already implicitly linked but " << "current configuration " << cd << " is not explicitly linked " << "with it"; ldb.verify_link (*cf, db); // Finally, turn the implicit link into explicit. // // Note: reuse id. // lcf->expl = true; lcf->name = move (name); lcf->path = rebase (ld, cd); // Note: can't clash (see above). db.update (lcf); } else { // If the directory path of the configuration being linked is relative // or the --relative option is specified, then rebase it relative to the // current configuration directory path. // lcf = make_shared (uid, move (name), move (type), rebase (ld, cd), true /* explicit */); db.persist (lcf); // Now implicitly link ourselves with the just linked configuration. // Note that we link ourselves as unnamed. // shared_ptr ccf (db.load (0)); // What if we find the current configuration to already be implicitly // linked? The potential scenario could be, that the current // configuration was recreated from scratch, previously being implicitly // linked with the configuration we currently link. It feels like in // this case we would rather overwrite the existing dead implicit link // than just fail. Let's also warn for good measure. // shared_ptr cf; for (shared_ptr lc: pointer_result (ldb.query (query::id != 0))) { if (cd == lc->make_effective_path (ld)) { if (lc->expl) fail << "current configuration " << cd << " is already linked " << "with " << ld; warn << "current configuration " << cd << " is already implicitly " << "linked with " << ld; cf = move (lc); continue; } if (ccf->uuid == lc->uuid) fail << "current configuration " << ccf->uuid << " is already " << "linked with " << ld; } // It feels natural to persist explicitly and implicitly linked // configuration paths both either relative or absolute. // if (cf != nullptr) { // The dead implicit link case. // // Note: reuse id. // cf->uuid = ccf->uuid; cf->type = move (ccf->type); cf->path = rebase (cd, ld); ldb.update (cf); } else { ccf = make_shared (ccf->uuid, nullopt /* name */, move (ccf->type), rebase (cd, ld), false /* explicit */); ldb.persist (ccf); } } // If explicit links of the current database are pre-attached, then also // pre-attach explicit links of the newly linked database. // linked_configs& lcs (db.explicit_links ()); if (!lcs.empty ()) { lcs.push_back (linked_config {*lcf->id, lcf->name, ldb}); ldb.attach_explicit (sys_rep); } // If the implicit links of the linked database are already cached, then // also cache the current database, unless it is already there (see above // for the dead link case). // linked_databases& lds (ldb.implicit_links (false /* attach */)); if (!lds.empty () && find (lds.begin (), lds.end (), db) == lds.end ()) lds.push_back (db); return lcf; } int cfg_link (const cfg_link_options& o, cli::scanner& args) try { tracer trace ("cfg_link"); dir_path c (o.directory ()); l4 ([&]{trace << "configuration: " << c;}); if (o.name_specified ()) validate_configuration_name (o.name (), "--name option value"); if (!args.more ()) fail << "configuration directory argument expected" << info << "run 'bpkg help cfg-link' for more information"; dir_path ld (args.next ()); if (ld.empty ()) throw invalid_path (ld.string ()); l4 ([&]{trace << "link configuration: " << ld;}); bool rel (ld.relative () || o.relative ()); normalize (ld, "specified linked configuration"); database db (c, trace, false /* pre_attach */, false /* sys_rep */, {ld}); transaction t (db); shared_ptr lc ( cfg_link (db, ld, rel, o.name_specified () ? o.name () : optional ())); t.commit (); if (verb && !o.no_result ()) { diag_record dr (text); dr << "linked with configuration " << ld << '\n' << " uuid: " << lc->uuid << '\n' << " type: " << lc->type << '\n'; if (lc->name) dr << " name: " << *lc->name << '\n'; dr << " id: " << *lc->id; } return 0; } catch (const invalid_path& e) { fail << "invalid path: '" << e.path << "'" << endf; } }