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