diff --git a/codeflash/cli_cmds/cli.py b/codeflash/cli_cmds/cli.py index d76e60a11..2a445cc7f 100644 --- a/codeflash/cli_cmds/cli.py +++ b/codeflash/cli_cmds/cli.py @@ -478,5 +478,12 @@ def _build_parser() -> ArgumentParser: action="store_true", help="Subagent mode: skip all interactive prompts with sensible defaults. Designed for AI agent integrations.", ) + parser.add_argument( + "--since-commit", + type=str, + default=None, + help="Optimize functions changed since this commit hash (diff from this commit to HEAD). " + "Useful when multiple commits were made in a session.", + ) return parser diff --git a/codeflash/code_utils/git_utils.py b/codeflash/code_utils/git_utils.py index 21afc3bd7..7cb31416c 100644 --- a/codeflash/code_utils/git_utils.py +++ b/codeflash/code_utils/git_utils.py @@ -19,7 +19,11 @@ def get_git_diff( - repo_directory: Path | None = None, *, only_this_commit: Optional[str] = None, uncommitted_changes: bool = False + repo_directory: Path | None = None, + *, + only_this_commit: Optional[str] = None, + uncommitted_changes: bool = False, + since_commit: Optional[str] = None, ) -> dict[str, list[int]]: from codeflash.languages.registry import get_supported_extensions @@ -30,7 +34,12 @@ def get_git_diff( # Use all registered extensions (Python + JS/TS + Java etc.) rather than # current_language_support() which defaults to Python before language detection runs. supported_extensions = set(get_supported_extensions()) - if only_this_commit: + if since_commit: + # Diff from a base commit to HEAD — captures all changes across multiple commits + uni_diff_text = repository.git.diff( + since_commit, commit.hexsha, ignore_blank_lines=True, ignore_space_at_eol=True + ) + elif only_this_commit: uni_diff_text = repository.git.diff( only_this_commit + "^1", only_this_commit, ignore_blank_lines=True, ignore_space_at_eol=True ) diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index 9c8776f75..3a8b86e9f 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -213,6 +213,7 @@ def get_functions_to_optimize( project_root: Path, module_root: Path, previous_checkpoint_functions: dict[str, dict[str, str]] | None = None, + since_commit: str | None = None, ) -> tuple[dict[Path, list[FunctionToOptimize]], int, Path | None]: assert sum([bool(optimize_all), bool(replay_test), bool(file)]) <= 1, ( "Only one of optimize_all, replay_test, or file should be provided" @@ -313,10 +314,13 @@ def get_functions_to_optimize( functions[file] = [found_function] else: - logger.info("Finding all functions modified in the current git diff ...") + if since_commit: + logger.info("Finding all functions modified since commit %s ...", since_commit[:8]) + else: + logger.info("Finding all functions modified in the current git diff ...") console.rule() ph("cli-optimizing-git-diff") - functions = get_functions_within_git_diff(uncommitted_changes=False) + functions = get_functions_within_git_diff(uncommitted_changes=False, since_commit=since_commit) filtered_modified_functions, functions_count = filter_functions( functions, test_cfg.tests_root, ignore_paths, project_root, module_root, previous_checkpoint_functions ) @@ -325,8 +329,12 @@ def get_functions_to_optimize( return filtered_modified_functions, functions_count, trace_file_path -def get_functions_within_git_diff(uncommitted_changes: bool) -> dict[Path, list[FunctionToOptimize]]: - modified_lines: dict[str, list[int]] = get_git_diff(uncommitted_changes=uncommitted_changes) +def get_functions_within_git_diff( + uncommitted_changes: bool, since_commit: str | None = None +) -> dict[Path, list[FunctionToOptimize]]: + modified_lines: dict[str, list[int]] = get_git_diff( + uncommitted_changes=uncommitted_changes, since_commit=since_commit + ) return get_functions_within_lines(modified_lines) diff --git a/codeflash/optimization/optimizer.py b/codeflash/optimization/optimizer.py index 49eaa23e5..7e4586670 100644 --- a/codeflash/optimization/optimizer.py +++ b/codeflash/optimization/optimizer.py @@ -158,6 +158,7 @@ def get_optimizable_functions(self) -> tuple[dict[Path, list[FunctionToOptimize] project_root=project_root, module_root=module_root, previous_checkpoint_functions=self.args.previous_checkpoint_functions, + since_commit=getattr(self.args, "since_commit", None), ) # Remap discovered file paths from the original repo to the worktree so