Skip to content

Commit 3c9ae50

Browse files
authored
Add reset command to CLI (#133)
1 parent afbbe90 commit 3c9ae50

File tree

7 files changed

+546
-1
lines changed

7 files changed

+546
-1
lines changed

.codegen/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Codegen
2+
docs/
3+
examples/
4+
prompts/
5+
jupyter/
6+
.venv/
7+
codegen-system-prompt.txt
8+
9+
# Python cache files
10+
__pycache__/
11+
*.py[cod]
12+
*$py.class
13+
14+
# Keep config.toml and codemods
15+
!config.toml
16+
!codemods/
17+
!codemods/**

.codegen/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
organization_name = "codegen-sh"
2+
repo_name = "codegen-sdk"

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,4 @@ graph-sitter-types/out/**
6464
graph-sitter-types/typings/**
6565
coverage.json
6666
tests/integration/verified_codemods/codemod_data/repo_commits.json
67-
.codegen/*
6867
.benchmarks/*

src/codegen/cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from codegen.cli.commands.logout.main import logout_command
1111
from codegen.cli.commands.notebook.main import notebook_command
1212
from codegen.cli.commands.profile.main import profile_command
13+
from codegen.cli.commands.reset.main import reset_command
1314
from codegen.cli.commands.run.main import run_command
1415
from codegen.cli.commands.run_on_pr.main import run_on_pr_command
1516
from codegen.cli.commands.style_debug.main import style_debug_command
@@ -37,6 +38,7 @@ def main():
3738
main.add_command(style_debug_command)
3839
main.add_command(run_on_pr_command)
3940
main.add_command(notebook_command)
41+
main.add_command(reset_command)
4042

4143

4244
if __name__ == "__main__":
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from pathlib import Path
2+
3+
import click
4+
from pygit2.enums import FileStatus, ResetMode
5+
from pygit2.repository import Repository
6+
7+
from codegen.cli.auth.constants import CODEGEN_DIR
8+
from codegen.cli.git.repo import get_git_repo
9+
10+
11+
def is_codegen_file(filepath: Path) -> bool:
12+
"""Check if a file is in the .codegen directory."""
13+
return CODEGEN_DIR in filepath.parents
14+
15+
16+
def backup_codegen_files(repo: Repository) -> dict[str, tuple[bytes | None, bool]]:
17+
"""Backup .codegen files and track if they were staged.
18+
19+
Returns:
20+
Dict mapping filepath to (content, was_staged) tuple.
21+
content is None for deleted files.
22+
"""
23+
codegen_changes = {}
24+
for filepath, status in repo.status().items():
25+
if not is_codegen_file(Path(filepath)):
26+
continue
27+
28+
was_staged = bool(status & (FileStatus.INDEX_MODIFIED | FileStatus.INDEX_NEW | FileStatus.INDEX_DELETED | FileStatus.INDEX_RENAMED))
29+
30+
# Handle deleted files
31+
if status & (FileStatus.WT_DELETED | FileStatus.INDEX_DELETED):
32+
codegen_changes[filepath] = (None, was_staged)
33+
continue
34+
# Handle modified, new, or renamed files
35+
if status & (FileStatus.WT_MODIFIED | FileStatus.WT_NEW | FileStatus.INDEX_MODIFIED | FileStatus.INDEX_NEW | FileStatus.INDEX_RENAMED):
36+
file_path = Path(repo.workdir) / filepath
37+
if file_path.exists(): # Only read if file exists
38+
codegen_changes[filepath] = (file_path.read_bytes(), was_staged)
39+
40+
return codegen_changes
41+
42+
43+
def restore_codegen_files(repo: Repository, codegen_changes: dict[str, tuple[bytes | None, bool]]) -> None:
44+
"""Restore backed up .codegen files and their staged status."""
45+
for filepath, (content, was_staged) in codegen_changes.items():
46+
file_path = Path(repo.workdir) / filepath
47+
48+
if content is None: # Handle deleted files
49+
if file_path.exists():
50+
file_path.unlink()
51+
if was_staged:
52+
repo.index.remove(filepath)
53+
else: # Handle existing files
54+
file_path.parent.mkdir(parents=True, exist_ok=True)
55+
file_path.write_bytes(content)
56+
if was_staged:
57+
repo.index.add(filepath)
58+
59+
if codegen_changes:
60+
repo.index.write()
61+
62+
63+
def remove_untracked_files(repo: Repository) -> None:
64+
"""Remove untracked files except those in .codegen directory."""
65+
for filepath, status in repo.status().items():
66+
if not is_codegen_file(Path(filepath)) and status & FileStatus.WT_NEW:
67+
file_path = Path(repo.workdir) / filepath
68+
if file_path.exists(): # Only try to remove if file exists
69+
if file_path.is_file():
70+
file_path.unlink()
71+
elif file_path.is_dir():
72+
file_path.rmdir()
73+
74+
75+
@click.command(name="reset")
76+
def reset_command() -> None:
77+
"""Reset git repository while preserving all files in .codegen directory"""
78+
repo = get_git_repo()
79+
if not repo:
80+
click.echo("Not a git repository", err=True)
81+
return
82+
83+
try:
84+
# Backup .codegen files and their staged status
85+
codegen_changes = backup_codegen_files(repo)
86+
87+
# Reset everything
88+
repo.reset(repo.head.target, ResetMode.HARD)
89+
90+
# Restore .codegen files and their staged status
91+
restore_codegen_files(repo, codegen_changes)
92+
93+
# Remove untracked files except .codegen
94+
remove_untracked_files(repo)
95+
96+
click.echo(f"Reset complete. Repository has been restored to HEAD (preserving {CODEGEN_DIR}) and untracked files have been removed (except {CODEGEN_DIR})")
97+
except Exception as e:
98+
click.echo(f"Error: {e}", err=True)
99+
100+
101+
if __name__ == "__main__":
102+
reset_command()

tests/unit/codegen/cli/conftest.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
import subprocess
3+
from pathlib import Path
4+
5+
import pytest
6+
from click.testing import CliRunner
7+
8+
from codegen.cli.commands.init.main import init_command
9+
10+
11+
@pytest.fixture
12+
def sample_repository(tmp_path: Path):
13+
os.chdir(tmp_path)
14+
subprocess.run(["git", "init", str(tmp_path)], check=True)
15+
subprocess.run(["git", "config", "--local", "user.email", "[email protected]"], check=True)
16+
subprocess.run(["git", "config", "--local", "user.name", "Test User"], check=True)
17+
subprocess.run(["git", "commit", "--allow-empty", "-m", "Initial commit"], check=True)
18+
subprocess.run(["git", "remote", "add", "origin", "https://github.com/test/test.git"], check=True)
19+
return tmp_path
20+
21+
22+
@pytest.fixture()
23+
def runner():
24+
return CliRunner(mix_stderr=False)
25+
26+
27+
@pytest.fixture
28+
def initialized_repo(sample_repository: Path, runner: CliRunner):
29+
os.chdir(sample_repository)
30+
runner.invoke(init_command)
31+
subprocess.run(["git", "add", "."], cwd=sample_repository, check=True)
32+
subprocess.run(["git", "commit", "-m", "Initialize codegen"], cwd=sample_repository, check=True)
33+
return sample_repository

0 commit comments

Comments
 (0)