Skip to content

Commit e00aa43

Browse files
Copilotncrmro
andcommitted
Ensure .venv is added to project .gitignore
- Created _ensure_venv_in_gitignore() function to manage project .gitignore - Automatically creates or updates .gitignore to include .venv when venv is created - Handles existing .gitignore files gracefully by appending - Prevents duplication if .venv already exists in .gitignore - Added 3 new integration tests to verify gitignore handling - All 40 integration tests pass - All 14 unit tests for python_env pass Co-authored-by: ncrmro <8276365+ncrmro@users.noreply.github.com>
1 parent 093d528 commit e00aa43

2 files changed

Lines changed: 113 additions & 0 deletions

File tree

src/deepwork/cli/install.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,42 @@ def _create_tmp_directory(deepwork_dir: Path) -> None:
148148
)
149149

150150

151+
def _ensure_venv_in_gitignore(project_path: Path, venv_path: str) -> None:
152+
"""
153+
Ensure the virtual environment is in the project's .gitignore.
154+
155+
Creates or updates the project's .gitignore file to include the venv path.
156+
157+
Args:
158+
project_path: Path to project root directory
159+
venv_path: Path to virtual environment (e.g., ".venv")
160+
"""
161+
gitignore_path = project_path / ".gitignore"
162+
163+
# Read existing .gitignore if it exists
164+
if gitignore_path.exists():
165+
content = gitignore_path.read_text()
166+
lines = content.splitlines()
167+
else:
168+
content = ""
169+
lines = []
170+
171+
# Check if venv_path is already in .gitignore (with or without trailing slash)
172+
venv_patterns = {venv_path, f"{venv_path}/", f"/{venv_path}", f"/{venv_path}/"}
173+
if any(line.strip() in venv_patterns for line in lines):
174+
return # Already present
175+
176+
# Add venv to .gitignore
177+
if content and not content.endswith("\n"):
178+
content += "\n"
179+
180+
if content:
181+
content += "\n"
182+
183+
content += f"# Python virtual environment (added by DeepWork)\n{venv_path}\n"
184+
gitignore_path.write_text(content)
185+
186+
151187
def _create_rules_directory(project_path: Path) -> bool:
152188
"""
153189
Create the v2 rules directory structure with example templates.
@@ -400,6 +436,9 @@ def _install_deepwork(platform_name: str | None, project_path: Path, python_mana
400436
success = env.setup(project_path)
401437
if success:
402438
console.print(" [green]✓[/green] Virtual environment created")
439+
# Ensure venv is in project's .gitignore
440+
_ensure_venv_in_gitignore(project_path, python_config["venv_path"])
441+
console.print(" [green]✓[/green] Added .venv to .gitignore")
403442
else:
404443
console.print(" [yellow]⚠[/yellow] Virtual environment setup returned False")
405444
except RuntimeError as e:

tests/integration/test_install_python.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,77 @@ def test_install_interactive_prompt_skip(self, mock_claude_project: Path) -> Non
191191
assert config is not None
192192
assert "python" in config
193193
assert config["python"]["manager"] == "skip"
194+
195+
def test_install_creates_gitignore_with_venv(self, mock_claude_project: Path) -> None:
196+
"""Test that install creates .gitignore with .venv entry."""
197+
runner = CliRunner()
198+
199+
result = runner.invoke(
200+
cli,
201+
[
202+
"install",
203+
"--platform", "claude",
204+
"--path", str(mock_claude_project),
205+
"--python-manager", "uv"
206+
],
207+
catch_exceptions=False,
208+
)
209+
210+
# Verify .gitignore was created
211+
gitignore_path = mock_claude_project / ".gitignore"
212+
assert gitignore_path.exists()
213+
214+
# Verify .venv is in .gitignore
215+
gitignore_content = gitignore_path.read_text()
216+
assert ".venv" in gitignore_content
217+
218+
def test_install_appends_to_existing_gitignore(self, mock_claude_project: Path) -> None:
219+
"""Test that install appends .venv to existing .gitignore."""
220+
# Create existing .gitignore
221+
gitignore_path = mock_claude_project / ".gitignore"
222+
original_content = "*.pyc\n__pycache__/\n"
223+
gitignore_path.write_text(original_content)
224+
225+
runner = CliRunner()
226+
227+
result = runner.invoke(
228+
cli,
229+
[
230+
"install",
231+
"--platform", "claude",
232+
"--path", str(mock_claude_project),
233+
"--python-manager", "uv"
234+
],
235+
catch_exceptions=False,
236+
)
237+
238+
# Verify original content is preserved
239+
gitignore_content = gitignore_path.read_text()
240+
assert "*.pyc" in gitignore_content
241+
assert "__pycache__/" in gitignore_content
242+
243+
# Verify .venv was added
244+
assert ".venv" in gitignore_content
245+
246+
def test_install_does_not_duplicate_venv_in_gitignore(self, mock_claude_project: Path) -> None:
247+
"""Test that install doesn't duplicate .venv if already in .gitignore."""
248+
# Create .gitignore with .venv already present
249+
gitignore_path = mock_claude_project / ".gitignore"
250+
gitignore_path.write_text(".venv\n")
251+
252+
runner = CliRunner()
253+
254+
result = runner.invoke(
255+
cli,
256+
[
257+
"install",
258+
"--platform", "claude",
259+
"--path", str(mock_claude_project),
260+
"--python-manager", "uv"
261+
],
262+
catch_exceptions=False,
263+
)
264+
265+
# Verify .venv appears only once
266+
gitignore_content = gitignore_path.read_text()
267+
assert gitignore_content.count(".venv") == 1

0 commit comments

Comments
 (0)