1414from codeflash .code_utils import env_utils
1515from codeflash .code_utils .code_utils import cleanup_paths , get_run_tmp_file
1616from codeflash .code_utils .env_utils import get_pr_number , is_pr_draft
17+ from codeflash .code_utils .git_utils import (
18+ check_running_in_git_repo ,
19+ create_detached_worktree ,
20+ create_diff_from_worktree ,
21+ create_worktree_snapshot_commit ,
22+ remove_worktree ,
23+ )
1724from codeflash .either import is_successful
1825from codeflash .models .models import ValidCode
1926from codeflash .telemetry .posthog_cf import ph
@@ -48,6 +55,8 @@ def __init__(self, args: Namespace) -> None:
4855 self .functions_checkpoint : CodeflashRunCheckpoint | None = None
4956 self .current_function_being_optimized : FunctionToOptimize | None = None # current only for the LSP
5057 self .current_function_optimizer : FunctionOptimizer | None = None
58+ self .current_worktree : Path | None = None
59+ self .patch_files : list [Path ] = []
5160
5261 def run_benchmarks (
5362 self , file_to_funcs_to_optimize : dict [Path , list [FunctionToOptimize ]], num_optimizable_functions : int
@@ -252,6 +261,10 @@ def run(self) -> None:
252261 if self .args .no_draft and is_pr_draft ():
253262 logger .warning ("PR is in draft mode, skipping optimization" )
254263 return
264+
265+ if self .args .worktree :
266+ self .worktree_mode ()
267+
255268 cleanup_paths (Optimizer .find_leftover_instrumented_test_files (self .test_cfg .tests_root ))
256269
257270 function_optimizer = None
@@ -260,7 +273,6 @@ def run(self) -> None:
260273 file_to_funcs_to_optimize , num_optimizable_functions
261274 )
262275 optimizations_found : int = 0
263- function_iterator_count : int = 0
264276 if self .args .test_framework == "pytest" :
265277 self .test_cfg .concolic_test_root_dir = Path (
266278 tempfile .mkdtemp (dir = self .args .tests_root , prefix = "codeflash_concolic_" )
@@ -296,8 +308,8 @@ def run(self) -> None:
296308 except Exception as e :
297309 logger .debug (f"Could not rank functions in { original_module_path } : { e } " )
298310
299- for function_to_optimize in functions_to_optimize :
300- function_iterator_count += 1
311+ for i , function_to_optimize in enumerate ( functions_to_optimize ) :
312+ function_iterator_count = i + 1
301313 logger .info (
302314 f"Optimizing function { function_iterator_count } of { num_optimizable_functions } : "
303315 f"{ function_to_optimize .qualified_name } "
@@ -327,6 +339,22 @@ def run(self) -> None:
327339 )
328340 if is_successful (best_optimization ):
329341 optimizations_found += 1
342+ if self .current_worktree :
343+ read_writable_code = best_optimization .unwrap ().code_context .read_writable_code
344+ relative_file_paths = [
345+ code_string .file_path for code_string in read_writable_code .code_strings
346+ ]
347+ patch_path = create_diff_from_worktree (
348+ self .current_worktree ,
349+ relative_file_paths ,
350+ self .current_function_optimizer .function_to_optimize .qualified_name ,
351+ )
352+ self .patch_files .append (patch_path )
353+ if i < len (functions_to_optimize ) - 1 :
354+ create_worktree_snapshot_commit (
355+ self .current_worktree ,
356+ f"Optimizing { functions_to_optimize [i + 1 ].qualified_name } " ,
357+ )
330358 else :
331359 logger .warning (best_optimization .failure ())
332360 console .rule ()
@@ -337,6 +365,10 @@ def run(self) -> None:
337365 function_optimizer .cleanup_generated_files ()
338366
339367 ph ("cli-optimize-run-finished" , {"optimizations_found" : optimizations_found })
368+ if len (self .patch_files ) > 0 :
369+ logger .info (
370+ f"Created { len (self .patch_files )} patch(es) ({ [str (patch_path ) for patch_path in self .patch_files ]} )"
371+ )
340372 if self .functions_checkpoint :
341373 self .functions_checkpoint .cleanup ()
342374 if hasattr (self .args , "command" ) and self .args .command == "optimize" :
@@ -388,7 +420,46 @@ def cleanup_temporary_paths(self) -> None:
388420 if hasattr (get_run_tmp_file , "tmpdir" ):
389421 get_run_tmp_file .tmpdir .cleanup ()
390422 del get_run_tmp_file .tmpdir
391- cleanup_paths ([self .test_cfg .concolic_test_root_dir , self .replay_tests_dir ])
423+ paths_to_clean = [self .test_cfg .concolic_test_root_dir , self .replay_tests_dir ]
424+ if self .current_worktree :
425+ remove_worktree (self .current_worktree )
426+ cleanup_paths (paths_to_clean )
427+
428+ def worktree_mode (self ) -> None :
429+ if self .current_worktree :
430+ return
431+ project_root = self .args .project_root
432+ module_root = self .args .module_root
433+
434+ if check_running_in_git_repo (module_root ):
435+ relative_module_root = module_root .relative_to (project_root )
436+ relative_optimized_file = self .args .file .relative_to (project_root ) if self .args .file else None
437+ relative_tests_root = self .test_cfg .tests_root .relative_to (project_root )
438+ relative_benchmarks_root = (
439+ self .args .benchmarks_root .relative_to (project_root ) if self .args .benchmarks_root else None
440+ )
441+
442+ worktree_dir = create_detached_worktree (module_root )
443+ if worktree_dir is None :
444+ logger .warning ("Failed to create worktree. Skipping optimization." )
445+ return
446+ self .current_worktree = worktree_dir
447+ # TODO: use a helper function to mutate self.args and self.test_cfg
448+ self .args .module_root = worktree_dir / relative_module_root
449+ self .args .project_root = worktree_dir
450+ self .args .test_project_root = worktree_dir
451+ self .args .tests_root = worktree_dir / relative_tests_root
452+ if relative_benchmarks_root :
453+ self .args .benchmarks_root = worktree_dir / relative_benchmarks_root
454+
455+ self .test_cfg .project_root_path = worktree_dir
456+ self .test_cfg .tests_project_rootdir = worktree_dir
457+ self .test_cfg .tests_root = worktree_dir / relative_tests_root
458+ if relative_benchmarks_root :
459+ self .test_cfg .benchmark_tests_root = worktree_dir / relative_benchmarks_root
460+
461+ if relative_optimized_file is not None :
462+ self .args .file = worktree_dir / relative_optimized_file
392463
393464
394465def run_with_args (args : Namespace ) -> None :
0 commit comments