diff --git a/crates/zeta_cli/src/example.rs b/crates/zeta_cli/src/example.rs index 0e09fbc4953e22e7be8a42eb786fe02809a04ebf..3eec3bc0f508744c4c1e3b6552a82b9fdbfe1704 100644 --- a/crates/zeta_cli/src/example.rs +++ b/crates/zeta_cli/src/example.rs @@ -10,6 +10,7 @@ use std::{ use anyhow::{Context as _, Result}; use clap::ValueEnum; +use futures::AsyncWriteExt as _; use gpui::http_client::Url; use pulldown_cmark::CowStr; use serde::{Deserialize, Serialize}; @@ -199,13 +200,14 @@ impl NamedExample { #[allow(unused)] pub async fn setup_worktree(&self) -> Result { + let (repo_owner, repo_name) = self.repo_name()?; + let file_name = self.file_name(); + let worktrees_dir = env::current_dir()?.join("target").join("zeta-worktrees"); let repos_dir = env::current_dir()?.join("target").join("zeta-repos"); fs::create_dir_all(&repos_dir)?; fs::create_dir_all(&worktrees_dir)?; - let (repo_owner, repo_name) = self.repo_name()?; - let repo_dir = repos_dir.join(repo_owner.as_ref()).join(repo_name.as_ref()); if !repo_dir.is_dir() { fs::create_dir_all(&repo_dir)?; @@ -217,36 +219,81 @@ impl NamedExample { .await?; } - run_git( - &repo_dir, - &["fetch", "--depth", "1", "origin", &self.example.revision], - ) - .await?; - - let worktree_path = worktrees_dir.join(&self.name); + // Resolve the example to a revision, fetching it if needed. + let revision = run_git(&repo_dir, &["rev-parse", &self.example.revision]).await; + let revision = if let Ok(revision) = revision { + revision + } else { + run_git( + &repo_dir, + &["fetch", "--depth", "1", "origin", &self.example.revision], + ) + .await?; + let revision = run_git(&repo_dir, &["rev-parse", "FETCH_HEAD"]).await?; + if revision != self.example.revision { + run_git(&repo_dir, &["tag", &self.example.revision, &revision]).await?; + } + revision + }; + // Create the worktree for this example if needed. + let worktree_path = worktrees_dir.join(&file_name); if worktree_path.is_dir() { run_git(&worktree_path, &["clean", "--force", "-d"]).await?; run_git(&worktree_path, &["reset", "--hard", "HEAD"]).await?; - run_git(&worktree_path, &["checkout", &self.example.revision]).await?; + run_git(&worktree_path, &["checkout", revision.as_str()]).await?; } else { let worktree_path_string = worktree_path.to_string_lossy(); + run_git(&repo_dir, &["branch", "-f", &file_name, revision.as_str()]).await?; run_git( &repo_dir, - &[ - "worktree", - "add", - "-f", - &worktree_path_string, - &self.example.revision, - ], + &["worktree", "add", "-f", &worktree_path_string, &file_name], ) .await?; } + // Apply the uncommitted diff for this example. + if !self.example.uncommitted_diff.is_empty() { + let mut apply_process = smol::process::Command::new("git") + .current_dir(&worktree_path) + .args(&["apply", "-"]) + .stdin(std::process::Stdio::piped()) + .spawn()?; + + let mut stdin = apply_process.stdin.take().unwrap(); + stdin + .write_all(self.example.uncommitted_diff.as_bytes()) + .await?; + stdin.close().await?; + drop(stdin); + + let apply_result = apply_process.output().await?; + if !apply_result.status.success() { + anyhow::bail!( + "Failed to apply uncommitted diff patch with status: {}\nstderr:\n{}\nstdout:\n{}", + apply_result.status, + String::from_utf8_lossy(&apply_result.stderr), + String::from_utf8_lossy(&apply_result.stdout), + ); + } + } + Ok(worktree_path) } + fn file_name(&self) -> String { + self.name + .chars() + .map(|c| { + if c.is_whitespace() { + '-' + } else { + c.to_ascii_lowercase() + } + }) + .collect() + } + #[allow(unused)] fn repo_name(&self) -> Result<(Cow<'_, str>, Cow<'_, str>)> { // git@github.com:owner/repo.git