From 55ae71f427891fd0ceea3bbffefd88084f13bc88 Mon Sep 17 00:00:00 2001 From: chelsealong Date: Sat, 8 Aug 2026 10:45:54 +0000 Subject: [PATCH] fix(cli): add .adk/ to the .gitignore generated by adk create adk create only added .env to the generated agent-level .gitignore. Local storage writes runtime data (session db, artifacts) under .adk/, which was left untracked-but-committable. Generalize the gitignore helper to ensure both .env and .adk/ are present, while preserving existing entries and avoiding duplicates. Fixes #6647 --- src/google/adk/cli/cli_create.py | 20 ++++++++---- tests/unittests/cli/utils/test_cli_create.py | 34 ++++++++++++++++---- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/google/adk/cli/cli_create.py b/src/google/adk/cli/cli_create.py index 3e5a5b0ed9b..13bd42cd099 100644 --- a/src/google/adk/cli/cli_create.py +++ b/src/google/adk/cli/cli_create.py @@ -72,28 +72,36 @@ """ +_GENERATED_GITIGNORE_ENTRIES = (".env", ".adk/") + + def _ensure_dotenv_gitignored(agent_folder: str) -> None: - """Ensures generated secrets are excluded from version control.""" + """Ensures generated secrets and local runtime data are excluded from + version control.""" gitignore_file_path = os.path.join(agent_folder, ".gitignore") - dotenv_entry = ".env" if not os.path.exists(gitignore_file_path): with open(gitignore_file_path, "w", encoding="utf-8") as f: - f.write(f"{dotenv_entry}\n") + f.write("".join(f"{entry}\n" for entry in _GENERATED_GITIGNORE_ENTRIES)) return with open(gitignore_file_path, "r", encoding="utf-8") as f: content = f.read() existing_lines = content.splitlines() - if dotenv_entry in existing_lines: + missing_entries = [ + entry + for entry in _GENERATED_GITIGNORE_ENTRIES + if entry not in existing_lines + ] + if not missing_entries: return - # Append .env, ensuring proper newline separation. + # Append missing entries, ensuring proper newline separation. with open(gitignore_file_path, "a", encoding="utf-8") as f: if content and not content.endswith("\n"): f.write("\n") - f.write(f"{dotenv_entry}\n") + f.write("".join(f"{entry}\n" for entry in missing_entries)) def _generate_files( diff --git a/tests/unittests/cli/utils/test_cli_create.py b/tests/unittests/cli/utils/test_cli_create.py index 98e0a92f889..06f4f0d9ea3 100644 --- a/tests/unittests/cli/utils/test_cli_create.py +++ b/tests/unittests/cli/utils/test_cli_create.py @@ -69,7 +69,7 @@ def test_generate_files_with_api_key(agent_folder: Path) -> None: env_content = (agent_folder / ".env").read_text() assert "GOOGLE_API_KEY=dummy-key" in env_content assert "GOOGLE_GENAI_USE_ENTERPRISE=0" in env_content - assert (agent_folder / ".gitignore").read_text() == ".env\n" + assert (agent_folder / ".gitignore").read_text() == ".env\n.adk/\n" assert (agent_folder / "agent.py").exists() assert (agent_folder / "__init__.py").exists() @@ -162,7 +162,9 @@ def test_generate_files_appends_dotenv_to_existing_gitignore( str(agent_folder), model="gemini-2.0-flash-001", type="code" ) - assert (agent_folder / ".gitignore").read_text() == "__pycache__\n.env\n" + assert ( + agent_folder / ".gitignore" + ).read_text() == "__pycache__\n.env\n.adk/\n" def test_generate_files_appends_dotenv_to_existing_gitignore_with_newline( @@ -176,13 +178,31 @@ def test_generate_files_appends_dotenv_to_existing_gitignore_with_newline( str(agent_folder), model="gemini-2.0-flash-001", type="code" ) - assert (agent_folder / ".gitignore").read_text() == "__pycache__\n.env\n" + assert ( + agent_folder / ".gitignore" + ).read_text() == "__pycache__\n.env\n.adk/\n" def test_generate_files_does_not_duplicate_dotenv_gitignore_entry( agent_folder: Path, ) -> None: - """Existing .env ignore entries should not be duplicated.""" + """Existing .env and .adk/ ignore entries should not be duplicated.""" + agent_folder.mkdir(parents=True, exist_ok=True) + (agent_folder / ".gitignore").write_text("__pycache__\n.env\n.adk/\n") + + cli_create._generate_files( + str(agent_folder), model="gemini-2.0-flash-001", type="code" + ) + + assert ( + agent_folder / ".gitignore" + ).read_text() == "__pycache__\n.env\n.adk/\n" + + +def test_generate_files_adds_missing_adk_entry_to_existing_gitignore( + agent_folder: Path, +) -> None: + """A .gitignore missing only .adk/ should have it appended.""" agent_folder.mkdir(parents=True, exist_ok=True) (agent_folder / ".gitignore").write_text("__pycache__\n.env\n") @@ -190,7 +210,9 @@ def test_generate_files_does_not_duplicate_dotenv_gitignore_entry( str(agent_folder), model="gemini-2.0-flash-001", type="code" ) - assert (agent_folder / ".gitignore").read_text() == "__pycache__\n.env\n" + assert ( + agent_folder / ".gitignore" + ).read_text() == "__pycache__\n.env\n.adk/\n" # run_cmd @@ -274,7 +296,7 @@ def test_run_cmd_with_type_config( env_file = agent_dir / ".env" assert env_file.exists() assert "GOOGLE_API_KEY=test-key" in env_file.read_text() - assert (agent_dir / ".gitignore").read_text() == ".env\n" + assert (agent_dir / ".gitignore").read_text() == ".env\n.adk/\n" # Prompt helpers