1use anyhow::{Result, anyhow};
2use serde::Deserialize;
3use std::{fs, path::Path};
4use tempfile::TempDir;
5use util::command::new_smol_command;
6use walkdir::WalkDir;
7
8#[derive(Debug, Deserialize)]
9pub struct SetupConfig {
10 #[serde(rename = "base.sha")]
11 pub base_sha: String,
12}
13
14#[derive(Debug, Deserialize)]
15pub struct RepoInfo {
16 pub remote_url: String,
17 pub head_sha: String,
18}
19
20pub async fn run_git(repo_path: &Path, args: &[&str]) -> Result<String> {
21 let output = new_smol_command("git")
22 .current_dir(repo_path)
23 .args(args)
24 .output()
25 .await?;
26
27 if output.status.success() {
28 Ok(String::from_utf8(output.stdout)?.trim().to_string())
29 } else {
30 Err(anyhow!(
31 "Git command failed: {} with status: {}",
32 args.join(" "),
33 output.status
34 ))
35 }
36}
37
38pub async fn read_base_sha(framework_path: &Path) -> Result<String> {
39 let setup_path = framework_path.join("setup.json");
40 let setup_content = smol::unblock(move || std::fs::read_to_string(&setup_path)).await?;
41 let setup_config: SetupConfig = serde_json_lenient::from_str_lenient(&setup_content)?;
42 Ok(setup_config.base_sha)
43}
44
45pub async fn read_repo_info(exercise_path: &Path) -> Result<RepoInfo> {
46 let repo_info_path = exercise_path.join(".meta").join("repo_info.json");
47 println!("Reading repo info from: {}", repo_info_path.display());
48 let repo_info_content = smol::unblock(move || std::fs::read_to_string(&repo_info_path)).await?;
49 let repo_info: RepoInfo = serde_json_lenient::from_str_lenient(&repo_info_content)?;
50
51 // Remove any quotes from the strings
52 let remote_url = repo_info.remote_url.trim_matches('"').to_string();
53 let head_sha = repo_info.head_sha.trim_matches('"').to_string();
54
55 Ok(RepoInfo {
56 remote_url,
57 head_sha,
58 })
59}
60
61pub async fn setup_temp_repo(exercise_path: &Path, _base_sha: &str) -> Result<TempDir> {
62 let temp_dir = TempDir::new()?;
63
64 // Check if this is an internal exercise by looking for repo_info.json
65 let repo_info_path = exercise_path.join(".meta").join("repo_info.json");
66 if repo_info_path.exists() {
67 // This is an internal exercise, handle it differently
68 let repo_info = read_repo_info(exercise_path).await?;
69
70 // Clone the repository to the temp directory
71 let url = repo_info.remote_url;
72 let clone_path = temp_dir.path();
73 println!(
74 "Cloning repository from {} to {}",
75 url,
76 clone_path.display()
77 );
78 run_git(
79 &std::env::current_dir()?,
80 &["clone", &url, &clone_path.to_string_lossy()],
81 )
82 .await?;
83
84 // Checkout the specified commit
85 println!("Checking out commit: {}", repo_info.head_sha);
86 run_git(temp_dir.path(), &["checkout", &repo_info.head_sha]).await?;
87
88 println!("Successfully set up internal repository");
89 } else {
90 // Original code for regular exercises
91 // Copy the exercise files to the temp directory, excluding .docs and .meta
92 for entry in WalkDir::new(exercise_path).min_depth(0).max_depth(10) {
93 let entry = entry?;
94 let source_path = entry.path();
95
96 // Skip .docs and .meta directories completely
97 if source_path.starts_with(exercise_path.join(".docs"))
98 || source_path.starts_with(exercise_path.join(".meta"))
99 {
100 continue;
101 }
102
103 if source_path.is_file() {
104 let relative_path = source_path.strip_prefix(exercise_path)?;
105 let dest_path = temp_dir.path().join(relative_path);
106
107 // Make sure parent directories exist
108 if let Some(parent) = dest_path.parent() {
109 fs::create_dir_all(parent)?;
110 }
111
112 fs::copy(source_path, dest_path)?;
113 }
114 }
115
116 // Initialize git repo in the temp directory
117 run_git(temp_dir.path(), &["init"]).await?;
118 run_git(temp_dir.path(), &["add", "."]).await?;
119 run_git(temp_dir.path(), &["commit", "-m", "Initial commit"]).await?;
120
121 println!("Created temp repo without .docs and .meta directories");
122 }
123
124 Ok(temp_dir)
125}