LCOV - code coverage report
Current view: top level - src/esys/repo/sharing - discover_sharing.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 70 91 76.9 %
Date: 2026-11-07 22:18:26 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/sharing/discover_sharing.cpp
       3             :  * \brief Dependency discovery adapters (ADR-0042)
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2026 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/sharing/discover.h"
      20             : #include "esys/repo/sharing/gitmodules.h"
      21             : #include "esys/repo/sharing/identity.h"
      22             : #include "esys/repo/errorinfo.h"
      23             : #include "esys/repo/manifest.h"
      24             : #include "esys/repo/manifest/location.h"
      25             : #include "esys/repo/manifest/repository.h"
      26             : 
      27             : #include <set>
      28             : 
      29             : namespace esys::repo::sharing
      30             : {
      31             : namespace
      32             : {
      33             : 
      34          24 : std::string lookup_sha(const std::map<std::string, std::string> &map, const std::string &path)
      35             : {
      36          24 :     auto it = map.find(path);
      37          24 :     if (it == map.end()) return "";
      38          24 :     return it->second;
      39             : }
      40             : 
      41             : } // namespace
      42             : 
      43           5 : Result discover_submodules(const std::string &consumer_path, const std::string &parent_remote,
      44             :                            const std::string &gitmodules_text, const std::map<std::string, std::string> &head_gitlinks,
      45             :                            const std::map<std::string, std::string> &index_gitlinks,
      46             :                            const std::map<std::string, std::string> &worktree_gitlinks, std::vector<Occurrence> &out)
      47             : {
      48           5 :     out.clear();
      49           5 :     GitmodulesFile gm;
      50           5 :     Result r = gm.parse_text(gitmodules_text);
      51           5 :     if (r.error()) return r;
      52             : 
      53          13 :     for (const auto &entry : gm.get_entries())
      54             :     {
      55           8 :         Occurrence occ;
      56           8 :         occ.set_consumer_path(consumer_path);
      57           8 :         occ.set_dependency_path(entry.get_path());
      58           8 :         occ.set_declaration_kind(DeclarationKind::Submodule);
      59           8 :         occ.set_provenance(".gitmodules:" + entry.get_name());
      60           8 :         occ.set_original_url(entry.get_url());
      61             : 
      62           8 :         const std::string head = lookup_sha(head_gitlinks, entry.get_path());
      63           8 :         const std::string index = lookup_sha(index_gitlinks, entry.get_path());
      64           8 :         const std::string wt = lookup_sha(worktree_gitlinks, entry.get_path());
      65             : 
      66           8 :         const bool index_differs = !index.empty() && !head.empty() && index != head;
      67           8 :         const bool wt_differs = !wt.empty() && !head.empty() && wt != head;
      68           8 :         const bool index_wt_diff = !index.empty() && !wt.empty() && index != wt;
      69             : 
      70           8 :         if (index_differs || wt_differs || index_wt_diff)
      71             :         {
      72           1 :             occ.set_materialization_status(MaterializationStatus::Skipped);
      73           1 :             occ.set_error("gitlink disagreement between committed tree / index / worktree; skipping occurrence");
      74           1 :             out.push_back(occ);
      75           1 :             continue;
      76             :         }
      77             : 
      78           7 :         const std::string sha = !head.empty() ? head : (!index.empty() ? index : wt);
      79           7 :         occ.set_required_commit(sha);
      80           7 :         occ.set_required_revision(sha);
      81             : 
      82           7 :         if (Identity::is_file_remote(entry.get_url()))
      83             :         {
      84           0 :             occ.set_error("file:// / filesystem remote out of v1 for sharing match");
      85           0 :             out.push_back(occ);
      86           0 :             continue;
      87             :         }
      88             : 
      89           7 :         auto id = Identity::normalize(entry.get_url(), parent_remote);
      90           7 :         if (id.error())
      91             :         {
      92           0 :             std::string msg = "identity normalize failed";
      93           0 :             if (id.get_error_info() && !id.get_error_info()->get_text().empty())
      94           0 :                 msg = id.get_error_info()->get_text();
      95           0 :             occ.set_error(msg);
      96           0 :             out.push_back(occ);
      97           0 :             continue;
      98           0 :         }
      99           7 :         occ.set_normalized_identity(id.get());
     100           7 :         out.push_back(occ);
     101           8 :     }
     102             : 
     103           5 :     return ESYSREPO_RESULT(ResultCode::OK);
     104           5 : }
     105             : 
     106           1 : Result discover_local_manifest(const std::string &consumer_path, const Manifest &manifest,
     107             :                                std::vector<Occurrence> &out)
     108             : {
     109           1 :     out.clear();
     110           2 :     for (const auto &loc : manifest.get_locations())
     111             :     {
     112           1 :         if (!loc) continue;
     113           2 :         for (const auto &repo : loc->get_repos())
     114             :         {
     115           1 :             if (!repo) continue;
     116           1 :             Occurrence occ;
     117           1 :             occ.set_consumer_path(consumer_path);
     118           1 :             occ.set_dependency_path(repo->get_path());
     119           1 :             occ.set_declaration_kind(DeclarationKind::LocalManifest);
     120           2 :             occ.set_provenance("local-manifest:" + loc->get_name() + "/" + repo->get_name());
     121           1 :             std::string url = Identity::compose_location_url(loc->get_address(), repo->get_name());
     122           1 :             occ.set_original_url(url);
     123           1 :             occ.set_required_revision(repo->get_revision());
     124             : 
     125           1 :             if (Identity::is_file_remote(url))
     126             :             {
     127           0 :                 occ.set_error("file:// / filesystem remote out of v1 for sharing match");
     128           0 :                 out.push_back(occ);
     129           0 :                 continue;
     130             :             }
     131             : 
     132           1 :             auto id = Identity::normalize(url);
     133           1 :             if (id.error())
     134             :             {
     135           0 :                 std::string msg = "identity normalize failed";
     136           0 :                 if (id.get_error_info() && !id.get_error_info()->get_text().empty())
     137           0 :                     msg = id.get_error_info()->get_text();
     138           0 :                 occ.set_error(msg);
     139           0 :                 out.push_back(occ);
     140           0 :                 continue;
     141           0 :             }
     142           1 :             occ.set_normalized_identity(id.get());
     143           1 :             out.push_back(occ);
     144           1 :         }
     145             :     }
     146           1 :     return ESYSREPO_RESULT(ResultCode::OK);
     147             : }
     148             : 
     149           1 : Result detect_path_conflicts(const std::vector<Occurrence> &occurrences)
     150             : {
     151           1 :     std::map<std::string, std::set<DeclarationKind>> kinds_by_path;
     152           4 :     for (const auto &occ : occurrences)
     153             :     {
     154           3 :         if (occ.get_dependency_path().empty()) continue;
     155           6 :         std::string key = occ.get_consumer_path() + std::string(1, '\x1f') + occ.get_dependency_path();
     156           3 :         kinds_by_path[key].insert(occ.get_declaration_kind());
     157           3 :     }
     158           1 :     for (const auto &pair : kinds_by_path)
     159             :     {
     160           1 :         if (pair.second.count(DeclarationKind::Submodule) && pair.second.count(DeclarationKind::LocalManifest))
     161             :         {
     162           1 :             return ESYSREPO_RESULT(ResultCode::SHARING_PATH_CONFLICT,
     163             :                                    "submodule and local-manifest declare the same consumer dependency path");
     164             :         }
     165             :     }
     166           0 :     return ESYSREPO_RESULT(ResultCode::OK);
     167           1 : }
     168             : 
     169             : } // namespace esys::repo::sharing

Generated by: LCOV version 1.14