git_commands.rs

  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}