Allow examples to use tags as revisions, and apply uncommitted diffs

Max Brunsfeld created

Change summary

crates/zeta_cli/src/example.rs | 81 ++++++++++++++++++++++++++++-------
1 file changed, 64 insertions(+), 17 deletions(-)

Detailed changes

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<PathBuf> {
+        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