pray_core/
resolve_git_sources.rs1use crate::lockfile::Lockfile;
2use crate::manifest::{ManifestPackage, ManifestSource};
3use crate::registry::{resolve_local_registry_package_root, RegistryPackageResolution};
4use crate::resolve_context::{PackageResolutionContext, ResolveOptions};
5use crate::resolve_git::{ensure_git_repository, local_git_source_root, resolve_distribution_root};
6use crate::{PrayError, PrayResult};
7use std::collections::BTreeMap;
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone)]
11pub(crate) struct GitSourceCheckout {
12 pub(crate) cache_directory: PathBuf,
13 pub(crate) revision: String,
14 pub(crate) subdir: Option<String>,
15}
16
17pub(crate) fn prepare_pray_ssh_host_keys(
18 sources: &[ManifestSource],
19) -> PrayResult<BTreeMap<String, String>> {
20 use crate::client_trust::{effective_trust_home, gate_pray_ssh_host};
21 use crate::ssh_client::parse_pray_ssh_url;
22
23 let home = effective_trust_home()?;
24 let mut host_keys = BTreeMap::new();
25 for source in sources {
26 if source.kind != "pray_ssh" {
27 continue;
28 }
29 let target = parse_pray_ssh_url(&source.url)?;
30 let fingerprint = gate_pray_ssh_host(&home, &source.url, &target.host, target.port)?;
31 if !fingerprint.is_empty() {
32 host_keys.insert(source.name.clone(), fingerprint);
33 }
34 }
35 Ok(host_keys)
36}
37
38pub(crate) fn prepare_git_sources(
39 project_root: &Path,
40 sources: &[ManifestSource],
41 lockfile: Option<&Lockfile>,
42 options: &ResolveOptions,
43) -> PrayResult<BTreeMap<String, GitSourceCheckout>> {
44 let mut git_sources = BTreeMap::new();
45 for source in sources {
46 if source.kind != "git" {
47 continue;
48 }
49 let clone_url = source.url.strip_prefix("git+").unwrap_or(&source.url);
50 let pinned_revision = if options.refresh_source_revisions {
51 None
52 } else {
53 pinned_revision_for_source(lockfile, source)
54 };
55 let refresh = options.refresh_source_revisions;
56 if is_local_filesystem_source(clone_url) && local_git_repo_path(clone_url).is_none() {
57 if let Some(source_root) = local_git_source_root(clone_url) {
58 git_sources.insert(
59 source.name.clone(),
60 GitSourceCheckout {
61 cache_directory: source_root,
62 revision: String::new(),
63 subdir: source.subdir.clone(),
64 },
65 );
66 }
67 continue;
68 }
69 let (cache_directory, revision) = ensure_git_repository(
70 project_root,
71 clone_url,
72 refresh,
73 pinned_revision.as_deref(),
74 source.subdir.as_deref(),
75 )?;
76 git_sources.insert(
77 source.name.clone(),
78 GitSourceCheckout {
79 cache_directory,
80 revision,
81 subdir: source.subdir.clone(),
82 },
83 );
84 }
85 Ok(git_sources)
86}
87
88pub(crate) fn is_local_filesystem_source(clone_url: &str) -> bool {
89 clone_url.starts_with("file://") || Path::new(clone_url).is_absolute()
90}
91
92pub(crate) fn local_git_repo_path(clone_url: &str) -> Option<PathBuf> {
93 let path = if let Some(path) = clone_url.strip_prefix("file://") {
94 PathBuf::from(path)
95 } else {
96 PathBuf::from(clone_url)
97 };
98 if path.join(".git").is_dir() {
99 Some(path)
100 } else {
101 None
102 }
103}
104
105pub(crate) fn pinned_revision_for_source(
106 lockfile: Option<&Lockfile>,
107 source: &ManifestSource,
108) -> Option<String> {
109 if let Some(revision) = lockfile
110 .and_then(|lockfile| {
111 lockfile
112 .source
113 .iter()
114 .find(|entry| entry.name == source.name && entry.kind == "git")
115 })
116 .and_then(|entry| entry.revision.clone())
117 {
118 return Some(revision);
119 }
120 if source.kind != "git" {
121 return None;
122 }
123 source.rev.clone().or_else(|| source.tag.clone())
124}
125
126pub(crate) fn resolve_git_package_root(
127 project_root: &Path,
128 source_name: &str,
129 source_url: &str,
130 git_sources: &BTreeMap<String, GitSourceCheckout>,
131 declaration: &ManifestPackage,
132 context: &PackageResolutionContext,
133) -> PrayResult<RegistryPackageResolution> {
134 let clone_url = source_url.strip_prefix("git+").unwrap_or(source_url);
135 if let Some(checkout) = git_sources.get(source_name) {
136 let distribution_root =
137 resolve_distribution_root(&checkout.cache_directory, checkout.subdir.as_deref())?;
138 let source_key = if checkout.revision.is_empty() {
139 clone_url.to_string()
140 } else {
141 format!("{}@{}", clone_url, checkout.revision)
142 };
143 return resolve_local_registry_package_root(
144 project_root,
145 &source_key,
146 &distribution_root,
147 declaration,
148 context,
149 )
150 .map_err(|error| {
151 crate::resolve_git_refresh::annotate_missing_git_catalog(
152 error,
153 &declaration.name,
154 source_name,
155 &checkout.revision,
156 )
157 });
158 }
159 if let Some(source_root) = local_git_source_root(clone_url) {
160 return resolve_local_registry_package_root(
161 project_root,
162 clone_url,
163 &source_root,
164 declaration,
165 context,
166 );
167 }
168 Err(PrayError::Resolution(format!(
169 "git source {source_name} was not prepared"
170 )))
171}
172
173pub fn refresh_git_sources(manifest_path: &Path) -> PrayResult<()> {
174 let project_root = project_root_for_manifest(manifest_path)?;
175 let manifest_text = crate::manifest::read_manifest_text(manifest_path)?;
176 let manifest = crate::manifest::parse_manifest(&manifest_text)?;
177 for source in &manifest.sources {
178 if source.kind != "git" {
179 continue;
180 }
181 let clone_url = source.url.strip_prefix("git+").unwrap_or(&source.url);
182 if is_local_filesystem_source(clone_url) && local_git_repo_path(clone_url).is_none() {
183 continue;
184 }
185 let _ = ensure_git_repository(
186 &project_root,
187 clone_url,
188 true,
189 None,
190 source.subdir.as_deref(),
191 )?;
192 }
193 Ok(())
194}
195
196fn project_root_for_manifest(manifest_path: &Path) -> PrayResult<PathBuf> {
197 let root = match manifest_path.parent() {
198 Some(parent) if !parent.as_os_str().is_empty() => parent.to_path_buf(),
199 _ => PathBuf::from("."),
200 };
201 if root.is_absolute() {
202 return Ok(root);
203 }
204 let cwd = std::env::current_dir().map_err(|error| {
205 PrayError::Resolution(format!("failed to resolve project root from cwd: {error}"))
206 })?;
207 Ok(cwd.join(root))
208}