Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c595a7a
getting worktree to work in both lsp and normal cli mode
mohammedahmed18 Aug 10, 2025
b955ad8
worktrees changes
mohammedahmed18 Aug 11, 2025
4e3f9ec
revert sentry
mohammedahmed18 Aug 11, 2025
50df6d7
patch new line
mohammedahmed18 Aug 11, 2025
e32e93f
fixes
mohammedahmed18 Aug 12, 2025
ff78ce3
fixes
mohammedahmed18 Aug 12, 2025
203002d
fix console.quiet issue
mohammedahmed18 Aug 12, 2025
d041dfb
fix: quite console in lsp mode
mohammedahmed18 Aug 12, 2025
6a7da80
Merge branch 'main' into feat/detached-worktrees
Saga4 Aug 13, 2025
d8eb121
return the best explanation from the lsp optimization feature
mohammedahmed18 Aug 13, 2025
b6d6d40
Merge branch 'main' of github.com:codeflash-ai/codeflash into feat/de…
mohammedahmed18 Aug 13, 2025
6fb1544
Merge branch 'feat/detached-worktrees' of github.com:codeflash-ai/cod…
mohammedahmed18 Aug 13, 2025
48d6ccd
delimiter for lsp server logs
mohammedahmed18 Aug 13, 2025
e33ff21
merge main
mohammedahmed18 Aug 14, 2025
22b5065
Merge branch 'main' of github.com:codeflash-ai/codeflash into feat/de…
mohammedahmed18 Aug 14, 2025
9c41957
small fixes
mohammedahmed18 Aug 14, 2025
8b74abd
unnecessarily message log
mohammedahmed18 Aug 14, 2025
1500869
fix: ignore white spaces when applying temp patch to the detached wor…
mohammedahmed18 Aug 15, 2025
bdf1770
feat: optimize current diff
mohammedahmed18 Aug 15, 2025
32099b4
fixes and getting the worktree to work with PR and staging
mohammedahmed18 Aug 17, 2025
6e619aa
comment
mohammedahmed18 Aug 18, 2025
7fccd60
fix linting issue
mohammedahmed18 Aug 19, 2025
8addeb3
Merge branch 'main' into feat/detached-worktrees
mohammedahmed18 Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion codeflash/api/aiservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from pydantic.json import pydantic_encoder

from codeflash.cli_cmds.console import console, logger
from codeflash.code_utils.env_utils import get_codeflash_api_key, is_LSP_enabled
from codeflash.code_utils.env_utils import get_codeflash_api_key
from codeflash.code_utils.git_utils import get_last_commit_author_if_pr_exists, get_repo_owner_and_name
from codeflash.lsp.helpers import is_LSP_enabled
from codeflash.models.ExperimentMetadata import ExperimentMetadata
from codeflash.models.models import AIServiceRefinerRequest, CodeStringsMarkdown, OptimizedCandidate
from codeflash.telemetry.posthog_cf import ph
Expand Down
3 changes: 2 additions & 1 deletion codeflash/api/cfapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from codeflash.code_utils.env_utils import ensure_codeflash_api_key, get_codeflash_api_key, get_pr_number
from codeflash.code_utils.git_utils import get_current_branch, get_repo_owner_and_name, git_root_dir
from codeflash.github.PrComment import FileDiffContent, PrComment
from codeflash.lsp.helpers import is_LSP_enabled
from codeflash.version import __version__

if TYPE_CHECKING:
Expand Down Expand Up @@ -101,7 +102,7 @@ def get_user_id() -> Optional[str]:
if min_version and version.parse(min_version) > version.parse(__version__):
msg = "Your Codeflash CLI version is outdated. Please update to the latest version using `pip install --upgrade codeflash`."
console.print(f"[bold red]{msg}[/bold red]")
if console.quiet: # lsp
if is_LSP_enabled():
logger.debug(msg)
return f"Error: {msg}"
sys.exit(1)
Expand Down
1 change: 1 addition & 0 deletions codeflash/cli_cmds/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def parse_args() -> Namespace:
help="Path to the directory of the project, where all the pytest-benchmark tests are located.",
)
parser.add_argument("--no-draft", default=False, action="store_true", help="Skip optimization for draft PRs")
parser.add_argument("--worktree", default=False, action="store_true", help="Use worktree for optimization")

args, unknown_args = parser.parse_known_args()
sys.argv[:] = [sys.argv[0], *unknown_args]
Expand Down
5 changes: 5 additions & 0 deletions codeflash/cli_cmds/console.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import logging
import os
from contextlib import contextmanager
from itertools import cycle
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -28,6 +29,10 @@
DEBUG_MODE = logging.getLogger().getEffectiveLevel() == logging.DEBUG

console = Console()

if os.getenv("CODEFLASH_LSP"):
console.quiet = True

logging.basicConfig(
level=logging.INFO,
handlers=[RichHandler(rich_tracebacks=True, markup=False, console=console, show_path=False, show_time=False)],
Expand Down
12 changes: 8 additions & 4 deletions codeflash/code_utils/coverage_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,20 @@ def build_fully_qualified_name(function_name: str, code_context: CodeOptimizatio
return full_name


def generate_candidates(source_code_path: Path) -> list[str]:
def generate_candidates(source_code_path: Path) -> set[str]:
"""Generate all the possible candidates for coverage data based on the source code path."""
candidates = [source_code_path.name]
candidates = set()
candidates.add(source_code_path.name)
current_path = source_code_path.parent

last_added = source_code_path.name
while current_path != current_path.parent:
candidate_path = str(Path(current_path.name) / candidates[-1])
candidates.append(candidate_path)
candidate_path = str(Path(current_path.name) / last_added)
candidates.add(candidate_path)
last_added = candidate_path
current_path = current_path.parent
Comment on lines 46 to 53
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡️Codeflash found 8,048% (80.48x) speedup for generate_candidates in codeflash/code_utils/coverage_utils.py

⏱️ Runtime : 162 milliseconds 1.99 milliseconds (best of 365 runs)

📝 Explanation and details

The optimized code achieves a 81x speedup by eliminating expensive Path object operations in the main loop. The key optimization replaces the original approach that repeatedly calls current_path.parent and constructs Path(current_path.name) / last_added objects with a more efficient strategy using source_code_path.parts.

Key Changes:

  1. Pre-compute path parts: Instead of traversing up the directory tree with .parent calls, the code extracts all path components once using source_code_path.parts
  2. Replace Path construction with string formatting: The expensive str(Path(current_path.name) / last_added) operation (96% of original runtime) is replaced with simple string formatting f"{parts[i]}/{last_added}"
  3. Eliminate parent traversal loop: The while current_path != current_path.parent loop is replaced with a for loop that iterates through pre-computed parts

Why This Is Faster:

  • Path object construction and filesystem-style operations are expensive in Python
  • String concatenation with f-strings is much faster than Path operations
  • Array indexing (parts[i]) is faster than repeated method calls (.parent)
  • The optimization eliminates the bottleneck line that consumed 96% of the original runtime

Performance Characteristics:
The optimization shows excellent scaling - deeper directory structures see greater speedups (up to 104x faster for 1000-level nesting). All test cases benefit significantly, with the smallest improvement being 30% for simple cases and massive gains of 8000%+ for deeply nested paths.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 40 Passed
🌀 Generated Regression Tests 38 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 1 Passed
📊 Tests Coverage 100.0%
⚙️ Existing Unit Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
test_code_utils.py::test_generate_candidates 69.6μs 7.84μs 788%✅
🌀 Generated Regression Tests and Runtime
from __future__ import annotations

from pathlib import Path

# imports
import pytest  # used for our unit tests
from codeflash.code_utils.coverage_utils import generate_candidates

# unit tests

# ------------- Basic Test Cases -------------

def test_basic_single_file():
    # Basic case: file in root directory
    path = Path("foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.91μs -> 5.19μs (52.3% faster)

def test_basic_nested_file():
    # File in a nested directory
    path = Path("src/app/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 25.5μs -> 5.87μs (334% faster)

def test_basic_deep_nested_file():
    # File in a deeper nested directory
    path = Path("a/b/c/d/e/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 46.4μs -> 6.39μs (626% faster)
    # Should include all intermediate candidates
    expected = {
        "foo.py",
        "e/foo.py",
        "d/e/foo.py",
        "c/d/e/foo.py",
        "b/c/d/e/foo.py",
        "a/b/c/d/e/foo.py"
    }

# ------------- Edge Test Cases -------------

def test_edge_empty_path():
    # Edge: empty path
    path = Path("")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.03μs -> 4.93μs (42.7% faster)

def test_edge_root_path():
    # Edge: root path ("/foo.py" on Unix)
    path = Path("/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.00μs -> 5.35μs (30.9% faster)

def test_edge_single_directory():
    # Edge: file directly in a directory
    path = Path("dir/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 17.2μs -> 5.06μs (241% faster)

def test_edge_dot_in_path():
    # Edge: directories or files with dots in their names
    path = Path("a.b/c.d/foo.e.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 25.5μs -> 5.80μs (340% faster)

def test_edge_trailing_slash():
    # Edge: path with trailing slash
    path = Path("src/app/foo.py/")
    codeflash_output = generate_candidates(path); result = codeflash_output # 24.9μs -> 5.58μs (346% faster)

def test_edge_windows_path():
    # Edge: Windows-style path
    path = Path("src\\app\\foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.51μs -> 4.90μs (53.4% faster)

def test_edge_non_ascii_characters():
    # Edge: non-ASCII characters in path
    path = Path("src/üñîçødë/文件.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 26.1μs -> 6.15μs (324% faster)

def test_edge_path_with_spaces():
    # Edge: path with spaces
    path = Path("my project/code file.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.8μs -> 5.13μs (227% faster)

def test_edge_path_with_dot_and_slash():
    # Edge: path with leading "./"
    path = Path("./foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.34μs -> 4.83μs (52.1% faster)

def test_edge_path_with_parent_references():
    # Edge: path with parent directory references
    path = Path("a/b/../c/foo.py").resolve()
    codeflash_output = generate_candidates(path); result = codeflash_output # 61.5μs -> 5.59μs (1001% faster)

# ------------- Large Scale Test Cases -------------

def test_large_deeply_nested_path():
    # Large: deeply nested path (depth 20)
    parts = [f"dir{i}" for i in range(20)]
    path = Path(*parts, "foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 168μs -> 9.58μs (1662% faster)

    # Check that each candidate is present
    last = "foo.py"
    for i in range(1, 21):
        last = f"dir{20-i}/{last}"

def test_large_many_candidates():
    # Large: wide directory tree (simulate many similar files)
    # This test only checks one file but ensures no performance issue
    path = Path("/".join(f"dir{i}" for i in range(50)) + "/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 494μs -> 15.0μs (3197% faster)

def test_large_unique_candidates():
    # Large: ensure all candidates are unique
    path = Path("a/b/c/d/e/f/g/h/i/j/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 80.9μs -> 7.11μs (1037% faster)

def test_large_path_performance():
    # Large: path with many directories, performance check
    path = Path("/".join(f"level{i}" for i in range(100)) + "/file.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 1.32ms -> 30.7μs (4198% faster)

def test_large_various_path_types():
    # Large: mix of absolute and relative, dots, and unicode
    path = Path("/abs/üñîçødë/./rel/../file.py").resolve()
    codeflash_output = generate_candidates(path); result = codeflash_output # 23.8μs -> 4.38μs (445% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from __future__ import annotations

from pathlib import Path

# imports
import pytest  # used for our unit tests
from codeflash.code_utils.coverage_utils import generate_candidates

# unit tests

# ---------------------- BASIC TEST CASES ----------------------

def test_single_file_in_root():
    # File in the root directory
    path = Path("foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.68μs -> 5.17μs (48.6% faster)

def test_file_in_one_subdirectory():
    # File in a single subdirectory
    path = Path("src/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.9μs -> 5.14μs (228% faster)

def test_file_in_two_subdirectories():
    # File in two nested subdirectories
    path = Path("src/bar/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 24.5μs -> 5.68μs (331% faster)

def test_file_with_dot_in_name():
    # File with dots in the name
    path = Path("src/foo.bar.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.8μs -> 5.00μs (237% faster)

def test_file_with_multiple_extensions():
    # File with multiple extensions
    path = Path("src/foo.test.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.5μs -> 4.99μs (230% faster)

# ---------------------- EDGE TEST CASES ----------------------

def test_file_at_filesystem_root():
    # File at the filesystem root (Unix style)
    path = Path("/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 6.79μs -> 5.13μs (32.4% faster)

def test_file_in_deeply_nested_structure():
    # Deeply nested structure
    path = Path("a/b/c/d/e/f/g/h/i/j/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 82.7μs -> 7.27μs (1037% faster)
    expected = {
        "foo.py",
        "j/foo.py",
        "i/j/foo.py",
        "h/i/j/foo.py",
        "g/h/i/j/foo.py",
        "f/g/h/i/j/foo.py",
        "e/f/g/h/i/j/foo.py",
        "d/e/f/g/h/i/j/foo.py",
        "c/d/e/f/g/h/i/j/foo.py",
        "b/c/d/e/f/g/h/i/j/foo.py",
        "a/b/c/d/e/f/g/h/i/j/foo.py",
    }

def test_file_with_spaces():
    # File with spaces in the name and directories
    path = Path("my folder/another folder/my file.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 25.1μs -> 5.71μs (340% faster)

def test_file_with_unicode_characters():
    # File with unicode characters
    path = Path("src/测试.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 17.2μs -> 5.10μs (237% faster)

def test_file_with_leading_dot():
    # Hidden file (leading dot)
    path = Path(".hidden/foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.2μs -> 4.99μs (224% faster)

def test_file_with_empty_path():
    # Empty path should raise an error
    path = Path("")
    codeflash_output = generate_candidates(path); result = codeflash_output # 7.08μs -> 4.72μs (50.1% faster)

def test_file_with_trailing_slash():
    # Path with trailing slash (should treat as directory, not file)
    path = Path("src/foo.py/")
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.7μs -> 5.05μs (231% faster)

def test_file_with_multiple_separators():
    # Path with redundant separators
    path = Path("src//bar///foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 24.7μs -> 5.74μs (330% faster)

def test_file_with_parent_directory_reference():
    # Path with parent directory references
    path = Path("src/bar/../foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 32.7μs -> 5.78μs (466% faster)
    # Pathlib resolves "bar/../foo.py" to "src/foo.py"
    resolved_path = path.resolve().relative_to(Path.cwd())
    codeflash_output = generate_candidates(resolved_path); result = codeflash_output # 13.9μs -> 4.07μs (243% faster)

# ---------------------- LARGE SCALE TEST CASES ----------------------

def test_large_number_of_nested_directories():
    # Test with 1000 nested directories (limited to 1000 as per instruction)
    dirs = [f"dir{i}" for i in range(1000)]
    path = Path("/".join(dirs)) / "foo.py"
    codeflash_output = generate_candidates(path); result = codeflash_output # 80.6ms -> 991μs (8027% faster)
    # Should contain 1001 candidates: "foo.py", "dir999/foo.py", ..., "dir0/dir1/.../dir999/foo.py"
    expected = set()
    last = "foo.py"
    for i in reversed(range(1000)):
        last = f"dir{i}/{last}"
        expected.add(last)
    expected.add("foo.py")
    expected.add(str(path))

def test_performance_with_wide_directory_names():
    # Test with 1000-character directory names
    dirname = "a" * 1000
    path = Path(dirname) / "foo.py"
    codeflash_output = generate_candidates(path); result = codeflash_output # 18.2μs -> 6.26μs (191% faster)

def test_performance_with_long_file_name():
    # Test with a very long file name (255 chars, common max for filesystems)
    filename = "f" * 255 + ".py"
    path = Path("src") / filename
    codeflash_output = generate_candidates(path); result = codeflash_output # 16.6μs -> 5.29μs (213% faster)

def test_performance_with_many_candidates():
    # Test with 1000 directories, ensure set size is correct
    dirs = [f"d{i}" for i in range(1000)]
    path = Path(*dirs, "foo.py")
    codeflash_output = generate_candidates(path); result = codeflash_output # 78.5ms -> 748μs (10395% faster)

def test_path_with_non_ascii_and_long_names():
    # Test with a mix of unicode and long names
    dirname = "测试" * 200  # 600 characters
    filename = "文件" * 50 + ".py"  # 102 characters
    path = Path(dirname) / filename
    codeflash_output = generate_candidates(path); result = codeflash_output # 17.9μs -> 6.23μs (188% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from codeflash.code_utils.coverage_utils import generate_candidates
from pathlib import Path

def test_generate_candidates():
    generate_candidates(Path())
🔎 Concolic Coverage Tests and Runtime
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
codeflash_concolic_9d591alw/tmpkrdmrlwv/test_concolic_coverage.py::test_generate_candidates 7.26μs 4.90μs 48.3%✅

To test or edit this optimization locally git merge codeflash/optimize-pr649-2025-08-14T13.45.27

Suggested change
current_path = source_code_path.parent
last_added = source_code_path.name
while current_path != current_path.parent:
candidate_path = str(Path(current_path.name) / candidates[-1])
candidates.append(candidate_path)
candidate_path = str(Path(current_path.name) / last_added)
candidates.add(candidate_path)
last_added = candidate_path
current_path = current_path.parent
last_added = source_code_path.name
parts = source_code_path.parts
for i in range(len(parts) - 2, 0, -1):
candidate_path = f"{parts[i]}/{last_added}"
candidates.add(candidate_path)
last_added = candidate_path


candidates.add(str(source_code_path))
return candidates


Expand Down
19 changes: 8 additions & 11 deletions codeflash/code_utils/env_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from pathlib import Path
from typing import Any, Optional

from codeflash.cli_cmds.console import console, logger
from codeflash.cli_cmds.console import logger
from codeflash.code_utils.code_utils import exit_with_message
from codeflash.code_utils.formatter import format_code
from codeflash.code_utils.shell_utils import read_api_key_from_shell_config
from codeflash.lsp.helpers import is_LSP_enabled


def check_formatter_installed(formatter_cmds: list[str], exit_on_failure: bool = True) -> bool: # noqa
Expand All @@ -34,11 +35,12 @@ def check_formatter_installed(formatter_cmds: list[str], exit_on_failure: bool =

@lru_cache(maxsize=1)
def get_codeflash_api_key() -> str:
if console.quiet: # lsp
# prefer shell config over env var in lsp mode
api_key = read_api_key_from_shell_config()
else:
api_key = os.environ.get("CODEFLASH_API_KEY") or read_api_key_from_shell_config()
# prefer shell config over env var in lsp mode
api_key = (
read_api_key_from_shell_config()
if is_LSP_enabled()
else os.environ.get("CODEFLASH_API_KEY") or read_api_key_from_shell_config()
)

api_secret_docs_message = "For more information, refer to the documentation at [https://docs.codeflash.ai/getting-started/codeflash-github-actions#add-your-api-key-to-your-repository-secrets]." # noqa
if not api_key:
Expand Down Expand Up @@ -125,11 +127,6 @@ def is_ci() -> bool:
return bool(os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"))


@lru_cache(maxsize=1)
def is_LSP_enabled() -> bool:
return console.quiet


def is_pr_draft() -> bool:
"""Check if the PR is draft. in the github action context."""
event = get_cached_gh_event_data()
Expand Down
4 changes: 2 additions & 2 deletions codeflash/code_utils/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import isort

from codeflash.cli_cmds.console import console, logger
from codeflash.lsp.helpers import is_LSP_enabled


def generate_unified_diff(original: str, modified: str, from_file: str, to_file: str) -> str:
Expand Down Expand Up @@ -109,8 +110,7 @@ def format_code(
print_status: bool = True, # noqa
exit_on_failure: bool = True, # noqa
) -> str:
if console.quiet:
# lsp mode
if is_LSP_enabled():
exit_on_failure = False
with tempfile.TemporaryDirectory() as test_dir_str:
if isinstance(path, str):
Expand Down
85 changes: 84 additions & 1 deletion codeflash/code_utils/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
from functools import cache
from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

import git
from rich.prompt import Confirm
from unidiff import PatchSet

from codeflash.cli_cmds.console import logger
from codeflash.code_utils.compat import codeflash_cache_dir
from codeflash.code_utils.config_consts import N_CANDIDATES
from codeflash.lsp.helpers import is_LSP_enabled

if TYPE_CHECKING:
from git import Repo
Expand Down Expand Up @@ -192,3 +194,84 @@ def get_last_commit_author_if_pr_exists(repo: Repo | None = None) -> str | None:
return None
else:
return last_commit.author.name


worktree_dirs = codeflash_cache_dir / "worktrees"
patches_dir = codeflash_cache_dir / "patches"


def create_worktree_snapshot_commit(worktree_dir: Path, commit_message: str) -> None:
repository = git.Repo(worktree_dir, search_parent_directories=True)
repository.git.commit("-am", commit_message, "--no-verify")


def create_detached_worktree(module_root: Path) -> Optional[Path]:
if not check_running_in_git_repo(module_root):
logger.warning("Module is not in a git repository. Skipping worktree creation.")
return None
git_root = git_root_dir()
current_time_str = time.strftime("%Y%m%d-%H%M%S")
worktree_dir = worktree_dirs / f"{git_root.name}-{current_time_str}"

result = subprocess.run(
["git", "worktree", "add", "-d", str(worktree_dir)],
cwd=git_root,
check=True,
stdout=subprocess.DEVNULL if is_LSP_enabled() else None,
stderr=subprocess.DEVNULL if is_LSP_enabled() else None,
)
if result.returncode != 0:
logger.error(f"Failed to create worktree: {result.stderr}")
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we do this via the git module we're already using?


# Get uncommitted diff from the original repo
repository = git.Repo(module_root, search_parent_directories=True)
repository.git.add("-N", ".") # add the index for untracked files to be included in the diff
uni_diff_text = repository.git.diff(None, "HEAD", ignore_blank_lines=True, ignore_space_at_eol=True)

if not uni_diff_text.strip():
logger.info("No uncommitted changes to copy to worktree.")
return worktree_dir

# Write the diff to a temporary file
with tempfile.NamedTemporaryFile(mode="w+", suffix=".codeflash.patch", delete=False) as tmp_patch_file:
tmp_patch_file.write(uni_diff_text + "\n") # the new line here is a must otherwise the last hunk won't be valid
tmp_patch_file.flush()

patch_path = Path(tmp_patch_file.name).resolve()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're using a namedtempfile, we should use w mode directly since the file wouldn't exist and thus not truncated


# Apply the patch inside the worktree
try:
subprocess.run(["git", "apply", patch_path], cwd=worktree_dir, check=True)
create_worktree_snapshot_commit(worktree_dir, "Initial Snapshot")
except subprocess.CalledProcessError as e:
logger.error(f"Failed to apply patch to worktree: {e}")

return worktree_dir


def remove_worktree(worktree_dir: Path) -> None:
try:
repository = git.Repo(worktree_dir, search_parent_directories=True)
repository.git.worktree("remove", "--force", worktree_dir)
except Exception:
logger.exception(f"Failed to remove worktree: {worktree_dir}")


def create_diff_patch_from_worktree(worktree_dir: Path, files: list[str], fto_name: str) -> Path:
repository = git.Repo(worktree_dir, search_parent_directories=True)
uni_diff_text = repository.git.diff(None, "HEAD", *files, ignore_blank_lines=True, ignore_space_at_eol=True)

if not uni_diff_text:
logger.warning("No changes found in worktree.")
return None

if not uni_diff_text.endswith("\n"):
uni_diff_text += "\n"

# write to patches_dir
patches_dir.mkdir(parents=True, exist_ok=True)
patch_path = patches_dir / f"{worktree_dir.name}.{fto_name}.patch"
with patch_path.open("w", encoding="utf8") as f:
f.write(uni_diff_text)
return patch_path
4 changes: 0 additions & 4 deletions codeflash/lsp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
# Silence the console module to prevent stdout pollution
from codeflash.cli_cmds.console import console

console.quiet = True
Loading
Loading