|
1 | 1 | use std::collections::HashMap; |
2 | | -use std::hash::{Hash, Hasher}; |
3 | | -use std::path::{Path, PathBuf}; |
4 | | -use tracing::warn; |
| 2 | +use std::path::PathBuf; |
5 | 3 |
|
6 | | -use crate::config; |
| 4 | +#[path = "pattern_repositories/checkout.rs"] |
| 5 | +mod checkout; |
| 6 | +#[path = "pattern_repositories/git.rs"] |
| 7 | +mod git; |
| 8 | +#[path = "pattern_repositories/local.rs"] |
| 9 | +mod local; |
| 10 | +#[path = "pattern_repositories/run.rs"] |
| 11 | +mod run; |
7 | 12 |
|
8 | 13 | pub type PatternRepositoryMap = HashMap<String, PathBuf>; |
9 | 14 |
|
10 | | -pub fn resolve_pattern_repositories( |
11 | | - config: &config::Config, |
12 | | - repo_root: &Path, |
13 | | -) -> PatternRepositoryMap { |
14 | | - let mut resolved = HashMap::new(); |
15 | | - if config.pattern_repositories.is_empty() { |
16 | | - return resolved; |
17 | | - } |
18 | | - |
19 | | - for repo in &config.pattern_repositories { |
20 | | - if resolved.contains_key(&repo.source) { |
21 | | - continue; |
22 | | - } |
23 | | - |
24 | | - let source_path = Path::new(&repo.source); |
25 | | - if source_path.is_absolute() && source_path.is_dir() { |
26 | | - if let Ok(path) = source_path.canonicalize() { |
27 | | - resolved.insert(repo.source.clone(), path); |
28 | | - } |
29 | | - continue; |
30 | | - } |
31 | | - |
32 | | - let repo_relative = repo_root.join(&repo.source); |
33 | | - if repo_relative.is_dir() { |
34 | | - if let Ok(path) = repo_relative.canonicalize() { |
35 | | - resolved.insert(repo.source.clone(), path); |
36 | | - } |
37 | | - continue; |
38 | | - } |
39 | | - |
40 | | - if is_git_source(&repo.source) { |
41 | | - if let Some(path) = prepare_pattern_repository_checkout(&repo.source) { |
42 | | - resolved.insert(repo.source.clone(), path); |
43 | | - continue; |
44 | | - } |
45 | | - } |
46 | | - |
47 | | - warn!( |
48 | | - "Skipping pattern repository '{}' (not a readable local path or cloneable git source)", |
49 | | - repo.source |
50 | | - ); |
51 | | - } |
52 | | - |
53 | | - resolved |
54 | | -} |
55 | | - |
56 | | -/// Check whether a source string looks like a git URL and uses an allowed scheme. |
57 | | -/// Accepts `https://`, `ssh://`, `git@` (SSH shorthand), and `http://` (with `.git` suffix). |
58 | | -fn is_safe_git_url(source: &str) -> bool { |
59 | | - source.starts_with("https://") |
60 | | - || source.starts_with("ssh://") |
61 | | - || source.starts_with("git@") |
62 | | - || (source.starts_with("http://") && source.ends_with(".git")) |
63 | | -} |
64 | | - |
65 | | -fn is_git_source(source: &str) -> bool { |
66 | | - is_safe_git_url(source) |
67 | | -} |
68 | | - |
69 | | -fn prepare_pattern_repository_checkout(source: &str) -> Option<PathBuf> { |
70 | | - use std::process::Command; |
71 | | - |
72 | | - if !is_safe_git_url(source) { |
73 | | - warn!( |
74 | | - "Rejecting pattern repository '{}': only https://, ssh://, and git@ URLs are allowed", |
75 | | - source |
76 | | - ); |
77 | | - return None; |
78 | | - } |
79 | | - |
80 | | - let home_dir = dirs::home_dir()?; |
81 | | - let cache_root = home_dir.join(".diffscope").join("pattern_repositories"); |
82 | | - if std::fs::create_dir_all(&cache_root).is_err() { |
83 | | - return None; |
84 | | - } |
85 | | - |
86 | | - let mut hasher = std::collections::hash_map::DefaultHasher::new(); |
87 | | - source.hash(&mut hasher); |
88 | | - let repo_dir = cache_root.join(format!("{:x}", hasher.finish())); |
89 | | - |
90 | | - if repo_dir.is_dir() { |
91 | | - let pull_result = Command::new("git") |
92 | | - .arg("-C") |
93 | | - .arg(&repo_dir) |
94 | | - .arg("pull") |
95 | | - .arg("--ff-only") |
96 | | - .output(); |
97 | | - if let Err(err) = pull_result { |
98 | | - warn!( |
99 | | - "Unable to update cached pattern repository {}: {}", |
100 | | - source, err |
101 | | - ); |
102 | | - } |
103 | | - } else { |
104 | | - let clone_result = Command::new("git") |
105 | | - .arg("clone") |
106 | | - .arg("--depth") |
107 | | - .arg("1") |
108 | | - .arg(source) |
109 | | - .arg(&repo_dir) |
110 | | - .output(); |
111 | | - match clone_result { |
112 | | - Ok(output) if output.status.success() => {} |
113 | | - Ok(output) => { |
114 | | - let stderr = String::from_utf8_lossy(&output.stderr); |
115 | | - warn!( |
116 | | - "Failed to clone pattern repository {}: {}", |
117 | | - source, |
118 | | - stderr.trim() |
119 | | - ); |
120 | | - return None; |
121 | | - } |
122 | | - Err(err) => { |
123 | | - warn!("Failed to clone pattern repository {}: {}", source, err); |
124 | | - return None; |
125 | | - } |
126 | | - } |
127 | | - } |
128 | | - |
129 | | - if repo_dir.is_dir() { |
130 | | - Some(repo_dir) |
131 | | - } else { |
132 | | - None |
133 | | - } |
134 | | -} |
| 15 | +pub use run::resolve_pattern_repositories; |
135 | 16 |
|
136 | 17 | #[cfg(test)] |
137 | 18 | mod tests { |
138 | | - use super::*; |
| 19 | + use super::git::{is_git_source, is_safe_git_url}; |
139 | 20 |
|
140 | 21 | #[test] |
141 | 22 | fn test_is_git_source_https() { |
|
0 commit comments