LCOV - code coverage report
Current view: top level - src/esys/repo/manifest - xmlfileimpl_manifest.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 440 487 90.3 %
Date: 2026-11-07 22:18:26 Functions: 31 32 96.9 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/manifest/xmlfileimpl_manifest.cpp
       3             :  * \brief
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020 Michel Gillet
       9             :  * Distributed under the MIT License.
      10             :  * (See accompanying file LICENSE.txt or
      11             :  * copy at https://opensource.org/licenses/MIT)
      12             :  *
      13             :  * __legal_e__
      14             :  * \endcond
      15             :  *
      16             :  */
      17             : 
      18             : #include "esys/repo/esysrepo_prec.h"
      19             : #include "esys/repo/manifest/xmlfileimpl.h"
      20             : #include "esys/repo/manifest/xmlfile.h"
      21             : #include "esys/repo/manifest/depends.h"
      22             : #include "esys/repo/manifest/workspace.h"
      23             : #include "esys/repo/sharing/policy.h"
      24             : #include "esys/repo/manifest/syncs.h"
      25             : 
      26             : #include <esysfile/xml/writer.h>
      27             : #include <esysfile/xml/file.h>
      28             : #include <esysfile/xml/attr.h>
      29             : 
      30             : #include <fstream>
      31             : #include <sstream>
      32             : #include <string>
      33             : 
      34             : namespace esys::repo::manifest
      35             : {
      36             : 
      37          79 : XMLFileImpl::XMLFileImpl(XMLFile *self)
      38          79 :     : m_self(self)
      39             : {
      40          79 : }
      41             : 
      42         158 : XMLFileImpl::~XMLFileImpl() = default;
      43             : 
      44          73 : Result XMLFileImpl::read(const std::string &path)
      45             : {
      46          73 :     esysfile::xml::File xml_file;
      47             : 
      48          73 :     xml_file.set_root_node_name("esysrepo_manifest");
      49          73 :     int result = xml_file.read(path);
      50          73 :     if (result < 0)
      51             :     {
      52           0 :         self()->add_error(result, "XML parsing failed.");
      53           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_ERROR_PARSING_FILE, path);
      54             :     }
      55             : 
      56         146 :     if (self()->get_data() == nullptr) self()->set_data(std::make_shared<Manifest>());
      57             : 
      58          73 :     self()->get_data()->set_type(Type::ESYSREPO_MANIFEST);
      59             : 
      60         146 :     return read(xml_file.get_data());
      61          73 : }
      62             : 
      63          73 : Result XMLFileImpl::read(std::shared_ptr<esysfile::xml::Data> data)
      64             : {
      65          73 :     if (data->get_attrs().size() != 0)
      66             :     {
      67          86 :         Result result = read_root_attributes(data);
      68          43 :         if (result.error())
      69             :         {
      70           1 :             self()->add_error(result.get_result_code_int(), "read_root_attributes failed.");
      71           2 :             return ESYSREPO_RESULT(result);
      72             :         }
      73          43 :     }
      74             :     else
      75          60 :         self()->get_data()->set_kind(manifest::Kind::ISOLATED);
      76             : 
      77          72 :     Result result;
      78             : 
      79         230 :     for (auto el : data->get_elements())
      80             :     {
      81         164 :         if (el->get_name() == "location")
      82         315 :             result = read_location(el);
      83          59 :         else if ((el->get_name() == "default"))
      84         147 :             result = read_default(el);
      85          10 :         else if ((el->get_name() == "group"))
      86          15 :             result = read_group(el);
      87           5 :         else if (el->get_name() == "revision-set")
      88          12 :             result = read_revision_set(el);
      89             :         else
      90           1 :             result = unknown_element(el->get_name());
      91         164 :         if (result.error())
      92             :         {
      93           6 :             self()->add_error(result.get_result_code_int(), "Reading XML element failed : " + el->get_name());
      94           6 :             return result;
      95             :         }
      96         164 :     }
      97             : 
      98          66 :     Result depends_result = self()->get_data()->validate_depends();
      99          66 :     if (depends_result.error()) return report_depends_error(depends_result);
     100          64 :     return ESYSREPO_RESULT(ResultCode::OK);
     101          72 : }
     102             : 
     103           1 : int XMLFileImpl::find_depends_line_in_file(const std::string &filename, const Depends &edge)
     104             : {
     105           1 :     if (filename.empty()) return -1;
     106             : 
     107           1 :     std::ifstream in(filename);
     108           1 :     if (!in) return -1;
     109             : 
     110           2 :     const std::string needle = to_string(edge.get_selector()) + "=\"" + edge.get_value() + "\"";
     111           1 :     std::string line;
     112           1 :     int line_number = 0;
     113           6 :     while (std::getline(in, line))
     114             :     {
     115           6 :         ++line_number;
     116           6 :         if (line.find("depends") == std::string::npos) continue;
     117           1 :         if (line.find(needle) == std::string::npos) continue;
     118             :         return line_number;
     119             :     }
     120             :     return -1;
     121           2 : }
     122             : 
     123           2 : Result XMLFileImpl::report_depends_error(const Result &depends_result)
     124             : {
     125           2 :     int line_number = -1;
     126           2 :     std::shared_ptr<Depends> unresolved;
     127           4 :     if (self()->get_data() != nullptr)
     128             :     {
     129           5 :         for (auto &location : self()->get_data()->get_locations())
     130             :         {
     131           4 :             for (auto &repo : location->get_repos())
     132             :             {
     133           5 :                 for (auto &edge : repo->get_depends())
     134             :                 {
     135           3 :                     if (edge == nullptr) continue;
     136           3 :                     if (!edge->get_resolved_path().empty()) continue;
     137           1 :                     if (edge->get_selector() == DependsSelector::NOT_SET) continue;
     138           1 :                     unresolved = edge;
     139           1 :                     line_number = edge->get_line_number();
     140             :                     break;
     141             :                 }
     142           3 :                 if (unresolved != nullptr) break;
     143             :             }
     144           2 :             if (unresolved != nullptr) break;
     145             :         }
     146             :     }
     147             : 
     148           2 :     if (line_number <= 0 && unresolved != nullptr)
     149           1 :         line_number = find_depends_line_in_file(self()->get_filename(), *unresolved);
     150             : 
     151           2 :     std::string detail;
     152           8 :     if (depends_result.get_error_info() != nullptr) detail = depends_result.get_error_info()->get_text();
     153           2 :     if (detail.empty()) detail = "validate_depends failed.";
     154             : 
     155           2 :     std::ostringstream oss;
     156           2 :     if (!self()->get_filename().empty())
     157             :     {
     158           2 :         oss << self()->get_filename();
     159           2 :         if (line_number > 0) oss << ":" << line_number;
     160           2 :         oss << ": ";
     161             :     }
     162           0 :     else if (line_number > 0)
     163             :     {
     164           0 :         oss << "line " << line_number << ": ";
     165             :     }
     166           2 :     oss << detail;
     167             : 
     168           2 :     const std::string msg = oss.str();
     169           2 :     self()->add_error(depends_result.get_result_code_int(), msg, line_number);
     170           2 :     return ESYSREPO_RESULT(depends_result.get_result_code(), msg);
     171           3 : }
     172             : 
     173           2 : Result XMLFileImpl::unknown_element(const std::string &name)
     174             : {
     175           2 :     if (self()->get_parse_mode() == ParseMode::Tolerant) return ESYSREPO_RESULT(ResultCode::OK);
     176           0 :     return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, name);
     177             : }
     178             : 
     179           3 : Result XMLFileImpl::unknown_attribute(const std::string &context, const std::string &attr_name)
     180             : {
     181           3 :     if (self()->get_parse_mode() == ParseMode::Tolerant) return ESYSREPO_RESULT(ResultCode::OK);
     182           1 :     return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, context + ": " + attr_name);
     183             : }
     184             : 
     185          43 : Result XMLFileImpl::read_root_attributes(std::shared_ptr<esysfile::xml::Element> root_el)
     186             : {
     187         103 :     for (auto attr : root_el->get_attrs())
     188             :     {
     189         122 :         Result result = read_root_attribute(attr);
     190          62 :         if (result.error()) return ESYSREPO_RESULT(result);
     191         122 :     }
     192          42 :     return ESYSREPO_RESULT(ResultCode::OK);
     193             : }
     194             : 
     195          61 : Result XMLFileImpl::read_root_attribute(std::shared_ptr<esysfile::xml::Attr> attr)
     196             : {
     197          61 :     if (attr->get_name() == "kind")
     198             :     {
     199          41 :         manifest::Kind kind;
     200          41 :         Result result = convert(attr->get_value(), kind);
     201          41 :         if (result.error()) return ESYSREPO_RESULT(result, ResultCode::MANIFEST_KIND_UNKNOWN, attr->get_value());
     202             : 
     203          41 :         self()->get_data()->set_kind(kind);
     204          41 :         return ESYSREPO_RESULT(ResultCode::OK);
     205          41 :     }
     206          20 :     if (attr->get_name() == "schema-version")
     207             :     {
     208          18 :         const std::string &ver = attr->get_value();
     209          33 :         if (ver != "1.0" && ver != "1.1")
     210           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, ver);
     211          18 :         self()->get_data()->set_schema_version(ver);
     212          18 :         return ESYSREPO_RESULT(ResultCode::OK);
     213             :     }
     214           4 :     return unknown_attribute("esysrepo_manifest", attr->get_name());
     215             : }
     216             : 
     217         105 : Result XMLFileImpl::read_location(std::shared_ptr<esysfile::xml::Element> el)
     218             : {
     219         105 :     if (el->get_name() != "location") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     220             : 
     221         105 :     std::shared_ptr<Location> location = std::make_shared<Location>();
     222             : 
     223         105 :     auto attr = el->get_attr("name");
     224         105 :     if (attr != nullptr)
     225         105 :         location->set_name(attr->get_value());
     226             :     else
     227           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_LOCATION_WITHOUT_NAME);
     228             : 
     229         210 :     attr = el->get_attr("addr");
     230         105 :     if (attr != nullptr)
     231         105 :         location->set_address(attr->get_value());
     232             :     else
     233           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_LOCATION_WITHOUT_ADDRESS);
     234             : 
     235         210 :     attr = el->get_attr("alt_addr");
     236         105 :     if (attr != nullptr) location->set_alt_address(attr->get_value());
     237             : 
     238         105 :     Result result;
     239             : 
     240         514 :     for (auto child_el : el->get_elements())
     241             :     {
     242         415 :         if (child_el->get_name() == "repository")
     243        2070 :             result = read_repository(child_el, location);
     244             :         else
     245           1 :             result = unknown_element(child_el->get_name());
     246         415 :         if (result.error()) return result;
     247         415 :     }
     248             : 
     249         297 :     self()->get_data()->add_location(location);
     250          99 :     return ESYSREPO_RESULT(ResultCode::OK);
     251         315 : }
     252             : 
     253         414 : Result XMLFileImpl::read_repository(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Location> location)
     254             : {
     255         414 :     if (el->get_name() != "repository") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     256             : 
     257         414 :     auto repository = std::make_shared<Repository>();
     258         414 :     repository->set_location(location.get());
     259             : 
     260         414 :     auto attr = el->get_attr("name");
     261         414 :     if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_NAME);
     262         414 :     repository->set_name(attr->get_value());
     263             : 
     264         828 :     attr = el->get_attr("path");
     265         414 :     if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_PATH);
     266         414 :     repository->set_path(attr->get_value());
     267             : 
     268         828 :     attr = el->get_attr("revision");
     269         414 :     if (attr != nullptr) repository->set_revision(attr->get_value());
     270             : 
     271         828 :     attr = el->get_attr("sync-s");
     272         414 :     if (attr != nullptr)
     273             :     {
     274           1 :         bool sync_s = false;
     275           1 :         if (!parse_sync_s_value(attr->get_value(), sync_s))
     276           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "repository.sync-s");
     277           1 :         repository->set_sync_s(sync_s);
     278             :     }
     279             : 
     280         426 :     for (auto child_el : el->get_elements())
     281             :     {
     282          18 :         if (child_el->get_name() == "depends")
     283             :         {
     284          28 :             Result result = read_depends(child_el, repository);
     285           7 :             if (result.error()) return result;
     286           7 :         }
     287          11 :         else if (child_el->get_name() == "sharing")
     288             :         {
     289          16 :             Result result = read_sharing(child_el, repository);
     290           4 :             if (result.error()) return result;
     291           4 :         }
     292           7 :         else if (child_el->get_name() == "workspace")
     293             :         {
     294          28 :             Result result = read_workspace(child_el, repository);
     295           7 :             if (result.error()) return result;
     296           7 :         }
     297             :         else
     298             :         {
     299           0 :             Result result = unknown_element(child_el->get_name());
     300           0 :             if (result.error()) return result;
     301           0 :         }
     302          18 :     }
     303             : 
     304         816 :     location->add_repo(repository);
     305             : 
     306         408 :     return ESYSREPO_RESULT(ResultCode::OK);
     307         828 : }
     308             : 
     309           7 : Result XMLFileImpl::read_workspace(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
     310             : {
     311           7 :     if (el->get_name() != "workspace") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     312             : 
     313             :     // ADR-0043: <workspace nested="true"/> requires draft schema-version 1.1.
     314          14 :     if (self()->get_data()->get_schema_version() != "1.1")
     315             :     {
     316           2 :         return ESYSREPO_RESULT(ResultCode::WORKSPACE_REQUIRES_SCHEMA_1_1,
     317             :                                "<workspace> requires schema-version=\"1.1\" (got '" +
     318             :                                    self()->get_data()->get_schema_version() + "')");
     319             :     }
     320             : 
     321           6 :     if (repository->get_path() == "." || repository->get_path() == "./")
     322             :     {
     323           1 :         return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_ROOT,
     324             :                                "workspace root path=\".\" cannot carry <workspace nested=\"true\"/>");
     325             :     }
     326             : 
     327           5 :     auto nested_attr = el->get_attr("nested");
     328           5 :     if (nested_attr == nullptr)
     329           0 :         return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID, "workspace.nested is required");
     330             : 
     331           5 :     auto nested = WorkspaceConfig::parse_nested(nested_attr->get_value());
     332           5 :     if (!nested.has_value())
     333           0 :         return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID, "workspace.nested=" + nested_attr->get_value());
     334           5 :     if (!*nested)
     335           1 :         return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID,
     336             :                                "workspace.nested must be \"true\" (got \"" + nested_attr->get_value() + "\")");
     337             : 
     338           8 :     for (auto child_attr : el->get_attrs())
     339             :     {
     340           4 :         if (child_attr->get_name() != "nested")
     341           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "workspace." + child_attr->get_name());
     342           4 :     }
     343             : 
     344           4 :     if (!el->get_elements().empty())
     345           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, "workspace child elements not allowed");
     346             : 
     347           4 :     repository->set_workspace(WorkspaceConfig(true));
     348           4 :     return ESYSREPO_RESULT(ResultCode::OK);
     349           7 : }
     350             : 
     351           4 : Result XMLFileImpl::read_sharing(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
     352             : {
     353           4 :     if (el->get_name() != "sharing") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     354             : 
     355             :     // ADR-0040: <sharing> requires draft schema-version 1.1 (fail if absent / 1.0).
     356           8 :     if (self()->get_data()->get_schema_version() != "1.1")
     357             :     {
     358           2 :         return ESYSREPO_RESULT(ResultCode::SHARING_REQUIRES_SCHEMA_1_1,
     359             :                                "<sharing> requires schema-version=\"1.1\" (got '" +
     360             :                                    self()->get_data()->get_schema_version() + "')");
     361             :     }
     362             : 
     363           3 :     if (repository->get_path() == "." || repository->get_path() == "./")
     364             :     {
     365           1 :         return ESYSREPO_RESULT(ResultCode::SHARING_ROOT_PROVIDER,
     366             :                                "workspace root path=\".\" cannot carry <sharing>");
     367             :     }
     368             : 
     369           2 :     auto policy_attr = el->get_attr("policy");
     370           2 :     if (policy_attr == nullptr)
     371           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "sharing.policy is required");
     372             : 
     373           2 :     auto policy = sharing::Config::parse_policy(policy_attr->get_value());
     374           2 :     if (!policy.has_value())
     375           1 :         return ESYSREPO_RESULT(ResultCode::SHARING_UNKNOWN_POLICY, "sharing.policy=" + policy_attr->get_value());
     376             : 
     377           1 :     sharing::RevisionMismatch mismatch = sharing::RevisionMismatch::Warn;
     378           1 :     auto mismatch_attr = el->get_attr("revision-mismatch");
     379           1 :     if (mismatch_attr != nullptr)
     380             :     {
     381           1 :         auto parsed = sharing::Config::parse_revision_mismatch(mismatch_attr->get_value());
     382           1 :         if (!parsed.has_value())
     383           0 :             return ESYSREPO_RESULT(ResultCode::SHARING_UNKNOWN_REVISION_MISMATCH,
     384             :                                    "sharing.revision-mismatch=" + mismatch_attr->get_value());
     385           1 :         mismatch = *parsed;
     386             :     }
     387             : 
     388           3 :     for (auto child_attr : el->get_attrs())
     389             :     {
     390           2 :         const auto &n = child_attr->get_name();
     391           3 :         if (n != "policy" && n != "revision-mismatch")
     392             :         {
     393             :             // Unknown attributes on <sharing>: hard error (authorization must not be guessed).
     394             :             // Even in tolerant mode — ADR-0040 §6 covers unknown policy; extend same for attrs.
     395           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "sharing." + n);
     396             :         }
     397           2 :     }
     398             : 
     399           1 :     if (!el->get_elements().empty())
     400           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, "sharing child elements not allowed");
     401             : 
     402           1 :     repository->set_sharing(sharing::Config(*policy, mismatch));
     403           1 :     return ESYSREPO_RESULT(ResultCode::OK);
     404           6 : }
     405             : 
     406           7 : Result XMLFileImpl::read_depends(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
     407             : {
     408           7 :     if (el->get_name() != "depends") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     409             : 
     410           7 :     auto depends = std::make_shared<Depends>();
     411           7 :     depends->set_line_number(el->get_line_number());
     412           7 :     int identity_count = 0;
     413             : 
     414           7 :     auto attr = el->get_attr("path");
     415           7 :     if (attr != nullptr)
     416             :     {
     417           7 :         depends->set_selector(DependsSelector::PATH);
     418           7 :         depends->set_value(attr->get_value());
     419             :         ++identity_count;
     420             :     }
     421          14 :     attr = el->get_attr("name");
     422           7 :     if (attr != nullptr)
     423             :     {
     424           0 :         if (identity_count > 0)
     425           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
     426           0 :         depends->set_selector(DependsSelector::NAME);
     427           0 :         depends->set_value(attr->get_value());
     428             :         ++identity_count;
     429             :     }
     430          14 :     attr = el->get_attr("project");
     431           7 :     if (attr != nullptr)
     432             :     {
     433           0 :         if (identity_count > 0)
     434           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
     435           0 :         depends->set_selector(DependsSelector::PROJECT);
     436           0 :         depends->set_value(attr->get_value());
     437             :         ++identity_count;
     438             :     }
     439          14 :     attr = el->get_attr("url");
     440           7 :     if (attr != nullptr)
     441             :     {
     442           0 :         if (identity_count > 0)
     443           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
     444           0 :         depends->set_selector(DependsSelector::URL);
     445           0 :         depends->set_value(attr->get_value());
     446             :         ++identity_count;
     447             :     }
     448             : 
     449           7 :     if (identity_count != 1)
     450           0 :         return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_WITHOUT_IDENTITY, "repository=" + repository->get_path());
     451             : 
     452          14 :     for (auto child_attr : el->get_attrs())
     453             :     {
     454           7 :         const auto &n = child_attr->get_name();
     455           7 :         if (n != "path" && n != "name" && n != "project" && n != "url")
     456             :         {
     457           0 :             Result result = unknown_attribute("depends", n);
     458           0 :             if (result.error()) return result;
     459           0 :         }
     460           7 :     }
     461             : 
     462          14 :     repository->add_depends(depends);
     463           7 :     return ESYSREPO_RESULT(ResultCode::OK);
     464          14 : }
     465             : 
     466          49 : Result XMLFileImpl::read_default(std::shared_ptr<esysfile::xml::Element> el)
     467             : {
     468          49 :     if (el->get_name() != "default") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     469             : 
     470         148 :     for (auto attr : el->get_attrs())
     471             :     {
     472         198 :         Result result = read_default_attr(attr);
     473          99 :         if (result.error()) return result;
     474         198 :     }
     475          49 :     return ESYSREPO_RESULT(ResultCode::OK);
     476             : }
     477             : 
     478          99 : Result XMLFileImpl::read_default_attr(std::shared_ptr<esysfile::xml::Attr> attr)
     479             : {
     480          99 :     if (attr->get_name() == "revision")
     481          98 :         self()->get_data()->set_default_revision(attr->get_value());
     482          50 :     else if (attr->get_name() == "sync-j")
     483          92 :         self()->get_data()->set_default_job_count(atoi(attr->get_value().c_str()));
     484           4 :     else if (attr->get_name() == "revision-set")
     485           4 :         self()->get_data()->set_default_revision_set(attr->get_value());
     486           2 :     else if (attr->get_name() == "sync-s")
     487             :     {
     488           1 :         bool sync_s = false;
     489           1 :         if (!parse_sync_s_value(attr->get_value(), sync_s))
     490           0 :             return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "default.sync-s");
     491           2 :         self()->get_data()->set_default_sync_s(sync_s);
     492             :     }
     493             :     else
     494           2 :         return unknown_attribute("default", attr->get_name());
     495          98 :     return ESYSREPO_RESULT(ResultCode::OK);
     496             : }
     497             : 
     498           5 : Result XMLFileImpl::read_group(std::shared_ptr<esysfile::xml::Element> el)
     499             : {
     500           5 :     auto attr = el->get_attr("name");
     501           5 :     if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_GROUP_WITHOUT_NAME);
     502             : 
     503           5 :     std::shared_ptr<Group> group = std::make_shared<Group>();
     504           5 :     group->set_name(attr->get_value());
     505             : 
     506          12 :     for (auto child : el->get_elements())
     507             :     {
     508          28 :         Result result = read_group_repo(child, group);
     509           7 :         if (result.error()) return ESYSREPO_RESULT(result);
     510          14 :     }
     511             : 
     512          15 :     self()->get_data()->get_groups().add_group(group);
     513             : 
     514           5 :     return ESYSREPO_RESULT(ResultCode::OK);
     515          10 : }
     516             : 
     517           7 : Result XMLFileImpl::read_group_repo(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Group> group)
     518             : {
     519           7 :     if (el->get_name() != "repo") return unknown_element(el->get_name());
     520             : 
     521           7 :     auto attr = el->get_attr("path");
     522           7 :     if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_GROUP_REPO_WITHOUT_PATH);
     523             : 
     524           7 :     auto repo = self()->get_data()->find_repo_by_path(attr->get_value());
     525           7 :     if (repo == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPO_UNKNOWN, "path = " + attr->get_value());
     526             : 
     527          14 :     group->add_repo(repo);
     528           7 :     return ESYSREPO_RESULT(ResultCode::OK);
     529          14 : }
     530             : 
     531           4 : Result XMLFileImpl::read_revision_set(std::shared_ptr<esysfile::xml::Element> el)
     532             : {
     533           4 :     if (el->get_name() != "revision-set") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
     534             : 
     535           4 :     auto attr = el->get_attr("name");
     536           4 :     if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "revision-set missing name");
     537             : 
     538           4 :     auto revision_set = std::make_shared<RevisionSet>(attr->get_value());
     539          10 :     for (auto child : el->get_elements())
     540             :     {
     541          24 :         Result result = read_revision_set_child(child, revision_set);
     542           6 :         if (result.error()) return ESYSREPO_RESULT(result);
     543          12 :     }
     544             : 
     545          12 :     self()->get_data()->get_revision_sets().add_revision_set(revision_set);
     546           4 :     return ESYSREPO_RESULT(ResultCode::OK);
     547           8 : }
     548             : 
     549           6 : Result XMLFileImpl::read_revision_set_child(std::shared_ptr<esysfile::xml::Element> el,
     550             :                                            std::shared_ptr<RevisionSet> revision_set)
     551             : {
     552           6 :     if (el->get_name() == "default")
     553             :     {
     554           4 :         auto attr = el->get_attr("revision");
     555           4 :         if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "default missing revision");
     556           4 :         revision_set->set_default_revision(attr->get_value());
     557           4 :         return ESYSREPO_RESULT(ResultCode::OK);
     558           4 :     }
     559           2 :     if (el->get_name() == "repository")
     560             :     {
     561           2 :         auto path_attr = el->get_attr("path");
     562           2 :         auto rev_attr = el->get_attr("revision");
     563           2 :         if (path_attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_PATH);
     564           2 :         if (rev_attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "repository missing revision");
     565           2 :         revision_set->set_repo_revision(path_attr->get_value(), rev_attr->get_value());
     566           2 :         return ESYSREPO_RESULT(ResultCode::OK);
     567           4 :     }
     568           0 :     return unknown_element(el->get_name());
     569             : }
     570             : 
     571          10 : Result XMLFileImpl::write_xml()
     572             : {
     573          20 :     if (self()->get_data() == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_XML_DATA_NULLPTR);
     574             : 
     575          10 :     m_xml_data = std::make_shared<esysfile::xml::Data>();
     576          10 :     m_xml_data->set_root_node_name("esysrepo_manifest");
     577             : 
     578          20 :     if (self()->get_data()->get_kind() != manifest::Kind::ISOLATED)
     579             :     {
     580           0 :         std::string kind;
     581           0 :         Result result = convert(self()->get_data()->get_kind(), kind);
     582           0 :         if (result.error()) return ESYSREPO_RESULT(result);
     583           0 :         m_xml_data->add_attr("kind", kind);
     584           0 :     }
     585             : 
     586             :     // Emit schema-version when set, or when any repository carries <sharing>.
     587          10 :     {
     588          10 :         std::string schema = self()->get_data()->get_schema_version();
     589          10 :         if (schema.empty())
     590             :         {
     591          22 :             for (const auto &loc : self()->get_data()->get_locations())
     592             :             {
     593          10 :                 if (!loc) continue;
     594          85 :                 for (const auto &repo : loc->get_repos())
     595             :                 {
     596          75 :                     if (repo && repo->get_sharing().has_value())
     597             :                     {
     598           0 :                         schema = "1.1";
     599             :                         break;
     600             :                     }
     601             :                 }
     602          10 :                 if (!schema.empty()) break;
     603             :             }
     604             :         }
     605          14 :         if (!schema.empty()) m_xml_data->add_attr("schema-version", schema);
     606           0 :     }
     607             : 
     608          10 :     {
     609          20 :         Result result = write_default(m_xml_data);
     610          10 :         if (result.error()) return ESYSREPO_RESULT(result);
     611          10 :     }
     612             : 
     613          34 :     for (auto location : self()->get_data()->get_locations())
     614             :     {
     615          56 :         Result result = write(m_xml_data, location);
     616          14 :         if (result.error()) return ESYSREPO_RESULT(result);
     617          28 :     }
     618             : 
     619          24 :     for (auto group : self()->get_data()->get_groups().get_groups())
     620             :     {
     621          16 :         Result result = write(m_xml_data, group);
     622           4 :         if (result.error()) return ESYSREPO_RESULT(result);
     623           8 :     }
     624             : 
     625          22 :     for (auto revision_set : self()->get_data()->get_revision_sets().get_revision_sets())
     626             :     {
     627           8 :         Result result = write(m_xml_data, revision_set);
     628           2 :         if (result.error()) return ESYSREPO_RESULT(result);
     629           4 :     }
     630          10 :     return ESYSREPO_RESULT(ResultCode::OK);
     631             : }
     632             : 
     633          10 : Result XMLFileImpl::write_default(std::shared_ptr<esysfile::xml::Element> parent_el)
     634             : {
     635          10 :     auto data = self()->get_data();
     636             :     // Constructor default revision is "master"; omit <default> unless something is non-default
     637             :     // so existing 1.0 write round-trips stay unchanged.
     638          10 :     const bool has_jobs = data->get_default_job_count() != 1;
     639          10 :     const bool has_rev_set = !data->get_default_revision_set().empty();
     640          10 :     const bool has_nondefault_rev = !data->get_default_revision().empty() && data->get_default_revision() != "master";
     641          10 :     const bool has_sync_s = data->get_default_sync_s().has_value();
     642          10 :     if (!has_jobs && !has_rev_set && !has_nondefault_rev && !has_sync_s) return ESYSREPO_RESULT(ResultCode::OK);
     643             : 
     644           5 :     auto default_el = std::make_shared<esysfile::xml::Element>();
     645           5 :     default_el->set_name("default");
     646          10 :     if (!data->get_default_revision().empty()) default_el->add_attr("revision", data->get_default_revision());
     647           9 :     if (has_jobs) default_el->add_attr("sync-j", std::to_string(data->get_default_job_count()));
     648           6 :     if (has_rev_set) default_el->add_attr("revision-set", data->get_default_revision_set());
     649           6 :     if (has_sync_s) default_el->add_attr("sync-s", *data->get_default_sync_s() ? "true" : "false");
     650          10 :     parent_el->add_element(default_el);
     651           5 :     return ESYSREPO_RESULT(ResultCode::OK);
     652          20 : }
     653             : 
     654           8 : Result XMLFileImpl::write(const std::string &path)
     655             : {
     656           8 :     Result result = write_xml();
     657           8 :     if (result.error()) return ESYSREPO_RESULT(result);
     658             : 
     659           8 :     esysfile::xml::Writer writer;
     660             : 
     661           8 :     writer.set_indent(4);
     662          16 :     int result_int = writer.write(path, m_xml_data);
     663           8 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, result_int);
     664           8 :     return ESYSREPO_RESULT(ResultCode::OK);
     665           8 : }
     666             : 
     667           2 : Result XMLFileImpl::write(std::ostream &os)
     668             : {
     669           2 :     Result result = write_xml();
     670           2 :     if (result.error()) return ESYSREPO_RESULT(result);
     671             : 
     672           2 :     esysfile::xml::Writer writer;
     673             : 
     674           2 :     writer.set_indent(4);
     675           4 :     int result_int = writer.write(os, m_xml_data);
     676           2 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, result_int);
     677           2 :     return ESYSREPO_RESULT(ResultCode::OK);
     678           2 : }
     679             : 
     680          14 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Location> location)
     681             : {
     682          14 :     std::shared_ptr<esysfile::xml::Element> location_el = std::make_shared<esysfile::xml::Element>();
     683             : 
     684          14 :     location_el->set_name("location");
     685          14 :     location_el->add_attr("name", location->get_name());
     686          14 :     location_el->add_attr("addr", location->get_address());
     687             : 
     688          99 :     for (auto repo : location->get_repos())
     689             :     {
     690         340 :         Result result = write(location_el, repo);
     691          85 :         if (result.error()) return ESYSREPO_RESULT(result);
     692         170 :     }
     693             : 
     694          28 :     parent_el->add_element(location_el);
     695          14 :     return ESYSREPO_RESULT(ResultCode::OK);
     696          14 : }
     697             : 
     698          85 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Repository> repository)
     699             : {
     700          85 :     std::shared_ptr<esysfile::xml::Element> repo_el = std::make_shared<esysfile::xml::Element>();
     701             : 
     702          85 :     repo_el->set_name("repository");
     703          85 :     repo_el->add_attr("path", repository->get_path());
     704          85 :     repo_el->add_attr("name", repository->get_name());
     705         113 :     if (!repository->get_revision().empty()) repo_el->add_attr("revision", repository->get_revision());
     706          85 :     if (repository->get_sync_s().has_value())
     707           3 :         repo_el->add_attr("sync-s", *repository->get_sync_s() ? "true" : "false");
     708             : 
     709          87 :     for (const auto &depends : repository->get_depends())
     710             :     {
     711           2 :         if (depends == nullptr) continue;
     712           2 :         auto dep_el = std::make_shared<esysfile::xml::Element>();
     713           2 :         dep_el->set_name("depends");
     714           2 :         dep_el->add_attr(to_string(depends->get_selector()), depends->get_value());
     715           6 :         repo_el->add_element(dep_el);
     716           2 :     }
     717             : 
     718          85 :     if (repository->get_sharing().has_value())
     719             :     {
     720           1 :         const auto &cfg = *repository->get_sharing();
     721           1 :         auto share_el = std::make_shared<esysfile::xml::Element>();
     722           1 :         share_el->set_name("sharing");
     723           1 :         share_el->add_attr("policy", sharing::Config::to_string(cfg.get_policy()));
     724           1 :         share_el->add_attr("revision-mismatch", sharing::Config::to_string(cfg.get_revision_mismatch()));
     725           3 :         repo_el->add_element(share_el);
     726           1 :     }
     727             : 
     728          85 :     if (repository->is_nested_workspace())
     729             :     {
     730           1 :         auto ws_el = std::make_shared<esysfile::xml::Element>();
     731           1 :         ws_el->set_name("workspace");
     732           1 :         ws_el->add_attr("nested", "true");
     733           3 :         repo_el->add_element(ws_el);
     734           1 :     }
     735             : 
     736         170 :     parent_el->add_element(repo_el);
     737          85 :     return ESYSREPO_RESULT(ResultCode::OK);
     738          85 : }
     739             : 
     740           4 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Group> group)
     741             : {
     742           4 :     std::shared_ptr<esysfile::xml::Element> grp_el = std::make_shared<esysfile::xml::Element>();
     743             : 
     744           4 :     grp_el->set_name("group");
     745           4 :     grp_el->add_attr("name", group->get_name());
     746             : 
     747          12 :     for (auto repo : group->get_repos())
     748             :     {
     749           8 :         std::shared_ptr<esysfile::xml::Element> repo_el = std::make_shared<esysfile::xml::Element>();
     750             : 
     751           8 :         repo_el->set_name("repo");
     752           8 :         repo_el->add_attr("path", repo->get_path());
     753          24 :         grp_el->add_element(repo_el);
     754          16 :     }
     755             : 
     756           8 :     parent_el->add_element(grp_el);
     757           4 :     return ESYSREPO_RESULT(ResultCode::OK);
     758           4 : }
     759             : 
     760           2 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<RevisionSet> revision_set)
     761             : {
     762           2 :     auto set_el = std::make_shared<esysfile::xml::Element>();
     763           2 :     set_el->set_name("revision-set");
     764           2 :     set_el->add_attr("name", revision_set->get_name());
     765             : 
     766           2 :     if (!revision_set->get_default_revision().empty())
     767             :     {
     768           2 :         auto default_el = std::make_shared<esysfile::xml::Element>();
     769           2 :         default_el->set_name("default");
     770           2 :         default_el->add_attr("revision", revision_set->get_default_revision());
     771           6 :         set_el->add_element(default_el);
     772           2 :     }
     773             : 
     774           3 :     for (const auto &kv : revision_set->get_repo_revisions())
     775             :     {
     776           1 :         auto repo_el = std::make_shared<esysfile::xml::Element>();
     777           1 :         repo_el->set_name("repository");
     778           1 :         repo_el->add_attr("path", kv.first);
     779           1 :         repo_el->add_attr("revision", kv.second);
     780           3 :         set_el->add_element(repo_el);
     781           1 :     }
     782             : 
     783           4 :     parent_el->add_element(set_el);
     784           2 :     return ESYSREPO_RESULT(ResultCode::OK);
     785           2 : }
     786             : 
     787         637 : XMLFile *XMLFileImpl::self()
     788             : {
     789         637 :     return m_self;
     790             : }
     791             : 
     792           0 : const XMLFile *XMLFileImpl::self() const
     793             : {
     794           0 :     return m_self;
     795             : }
     796             : 
     797             : } // namespace esys::repo::manifest

Generated by: LCOV version 1.14