11use std:: {
2+ borrow:: Cow ,
3+ env,
24 fmt:: { self , Display } ,
5+ fs,
36 io:: Write ,
47 mem,
58 os:: unix:: ffi:: OsStrExt ,
69 path:: { Path , PathBuf } ,
710} ;
811
9- use anyhow:: Result ;
12+ use anyhow:: { Context as _ , Result } ;
1013use clap:: ValueEnum ;
14+ use gpui:: http_client:: Url ;
1115use pulldown_cmark:: CowStr ;
1216use serde:: { Deserialize , Serialize } ;
1317
@@ -18,23 +22,24 @@ const EXPECTED_EXCERPTS_HEADING: &str = "Expected Excerpts";
1822const REPOSITORY_URL_FIELD : & str = "repository_url" ;
1923const REVISION_FIELD : & str = "revision" ;
2024
25+ #[ derive( Debug ) ]
2126pub struct NamedExample {
22- name : String ,
23- example : Example ,
27+ pub name : String ,
28+ pub example : Example ,
2429}
2530
26- #[ derive( Serialize , Deserialize ) ]
31+ #[ derive( Debug , Serialize , Deserialize ) ]
2732pub struct Example {
28- repository_url : String ,
29- commit : String ,
30- cursor_path : PathBuf ,
31- cursor_position : String ,
32- edit_history : Vec < String > ,
33- expected_patch : String ,
34- expected_excerpts : Vec < ExpectedExcerpt > ,
33+ pub repository_url : String ,
34+ pub revision : String ,
35+ pub cursor_path : PathBuf ,
36+ pub cursor_position : String ,
37+ pub edit_history : Vec < String > ,
38+ pub expected_patch : String ,
39+ pub expected_excerpts : Vec < ExpectedExcerpt > ,
3540}
3641
37- #[ derive( Serialize , Deserialize ) ]
42+ #[ derive( Debug , Serialize , Deserialize ) ]
3843pub struct ExpectedExcerpt {
3944 path : PathBuf ,
4045 text : String ,
@@ -53,16 +58,16 @@ impl NamedExample {
5358 let content = std:: fs:: read_to_string ( path) ?;
5459 let ext = path. extension ( ) ;
5560
56- match ext. map ( |s| s. as_bytes ( ) ) {
57- Some ( b "json") => Ok ( Self {
61+ match ext. and_then ( |s| s. to_str ( ) ) {
62+ Some ( "json" ) => Ok ( Self {
5863 name : path. file_name ( ) . unwrap_or_default ( ) . display ( ) . to_string ( ) ,
5964 example : serde_json:: from_str ( & content) ?,
6065 } ) ,
61- Some ( b "toml") => Ok ( Self {
66+ Some ( "toml" ) => Ok ( Self {
6267 name : path. file_name ( ) . unwrap_or_default ( ) . display ( ) . to_string ( ) ,
6368 example : toml:: from_str ( & content) ?,
6469 } ) ,
65- Some ( b "md") => Self :: parse_md ( & content) ,
70+ Some ( "md" ) => Self :: parse_md ( & content) ,
6671 Some ( _) => {
6772 anyhow:: bail!( "Unrecognized example extension: {}" , ext. unwrap( ) . display( ) ) ;
6873 }
@@ -83,7 +88,7 @@ impl NamedExample {
8388 name : String :: new ( ) ,
8489 example : Example {
8590 repository_url : String :: new ( ) ,
86- commit : String :: new ( ) ,
91+ revision : String :: new ( ) ,
8792 cursor_path : PathBuf :: new ( ) ,
8893 cursor_position : String :: new ( ) ,
8994 edit_history : Vec :: new ( ) ,
@@ -106,12 +111,12 @@ impl NamedExample {
106111 // in h1 section
107112 && let Some ( ( field, value) ) = line. split_once ( '=' )
108113 {
109- match field {
114+ match field. trim ( ) {
110115 REPOSITORY_URL_FIELD => {
111- named. example . repository_url = value. to_string ( ) ;
116+ named. example . repository_url = value. trim ( ) . to_string ( ) ;
112117 }
113118 REVISION_FIELD => {
114- named. example . commit = value. to_string ( ) ;
119+ named. example . revision = value. trim ( ) . to_string ( ) ;
115120 }
116121 _ => {
117122 eprintln ! ( "Warning: Unrecognized field `{field}`" ) ;
@@ -188,6 +193,111 @@ impl NamedExample {
188193 ExampleFormat :: Md => Ok ( write ! ( out, "{}" , self ) ?) ,
189194 }
190195 }
196+
197+ pub async fn setup_worktree ( & self ) -> Result < PathBuf > {
198+ let worktrees_dir = env:: current_dir ( ) ?. join ( "target" ) . join ( "zeta-worktrees" ) ;
199+ let repos_dir = env:: current_dir ( ) ?. join ( "target" ) . join ( "zeta-repos" ) ;
200+ fs:: create_dir_all ( & repos_dir) ?;
201+ fs:: create_dir_all ( & worktrees_dir) ?;
202+
203+ let ( repo_owner, repo_name) = self . repo_name ( ) ?;
204+
205+ let repo_dir = repos_dir. join ( repo_owner. as_ref ( ) ) . join ( repo_name. as_ref ( ) ) ;
206+ dbg ! ( & repo_dir) ;
207+ if !repo_dir. is_dir ( ) {
208+ fs:: create_dir_all ( & repo_dir) ?;
209+ run_git ( & repo_dir, & [ "init" ] ) . await ?;
210+ run_git (
211+ & repo_dir,
212+ & [ "remote" , "add" , "origin" , & self . example . repository_url ] ,
213+ )
214+ . await ?;
215+ }
216+
217+ run_git (
218+ & repo_dir,
219+ & [ "fetch" , "--depth" , "1" , "origin" , & self . example . revision ] ,
220+ )
221+ . await ?;
222+
223+ let worktree_path = worktrees_dir. join ( & self . name ) ;
224+
225+ dbg ! ( & worktree_path) ;
226+
227+ if worktree_path. is_dir ( ) {
228+ run_git ( & worktree_path, & [ "clean" , "--force" , "-d" ] ) . await ?;
229+ run_git ( & worktree_path, & [ "reset" , "--hard" , "HEAD" ] ) . await ?;
230+ run_git ( & worktree_path, & [ "checkout" , & self . example . revision ] ) . await ?;
231+ } else {
232+ let worktree_path_string = worktree_path. to_string_lossy ( ) ;
233+ run_git (
234+ & repo_dir,
235+ & [
236+ "worktree" ,
237+ "add" ,
238+ "-f" ,
239+ & worktree_path_string,
240+ & self . example . revision ,
241+ ] ,
242+ )
243+ . await ?;
244+ }
245+
246+ Ok ( worktree_path)
247+ }
248+
249+ fn repo_name ( & self ) -> Result < ( Cow < str > , Cow < str > ) > {
250+ // [email protected] :owner/repo.git 251+ if self . example . repository_url . contains ( '@' ) {
252+ let ( owner, repo) = self
253+ . example
254+ . repository_url
255+ . split_once ( ':' )
256+ . context ( "expected : in git url" ) ?
257+ . 1
258+ . split_once ( '/' )
259+ . context ( "expected / in git url" ) ?;
260+ Ok ( (
261+ Cow :: Borrowed ( owner) ,
262+ Cow :: Borrowed ( repo. trim_end_matches ( ".git" ) ) ,
263+ ) )
264+ // http://github.com/owner/repo.git
265+ } else {
266+ let url = Url :: parse ( & self . example . repository_url ) ?;
267+ let mut segments = url. path_segments ( ) . context ( "empty http url" ) ?;
268+ let owner = segments
269+ . next ( )
270+ . context ( "expected owner path segment" ) ?
271+ . to_string ( ) ;
272+ let repo = segments
273+ . next ( )
274+ . context ( "expected repo path segment" ) ?
275+ . trim_end_matches ( ".git" )
276+ . to_string ( ) ;
277+ assert ! ( segments. next( ) . is_none( ) ) ;
278+
279+ Ok ( ( owner. into ( ) , repo. into ( ) ) )
280+ }
281+ }
282+ }
283+
284+ async fn run_git ( repo_path : & Path , args : & [ & str ] ) -> Result < String > {
285+ let output = smol:: process:: Command :: new ( "git" )
286+ . current_dir ( repo_path)
287+ . args ( args)
288+ . output ( )
289+ . await ?;
290+
291+ anyhow:: ensure!(
292+ output. status. success( ) ,
293+ "`git {}` within `{}` failed with status: {}\n stderr:\n {}\n stdout:\n {}" ,
294+ args. join( " " ) ,
295+ repo_path. display( ) ,
296+ output. status,
297+ String :: from_utf8_lossy( & output. stderr) ,
298+ String :: from_utf8_lossy( & output. stdout) ,
299+ ) ;
300+ Ok ( String :: from_utf8 ( output. stdout ) ?. trim ( ) . to_string ( ) )
191301}
192302
193303impl Display for NamedExample {
@@ -198,7 +308,7 @@ impl Display for NamedExample {
198308 "{REPOSITORY_URL_FIELD} = {}\n " ,
199309 self . example. repository_url
200310 ) ?;
201- write ! ( f, "{REVISION_FIELD} = {}\n \n " , self . example. commit ) ?;
311+ write ! ( f, "{REVISION_FIELD} = {}\n \n " , self . example. revision ) ?;
202312
203313 write ! (
204314 f,
0 commit comments