diff --git a/aider/args.py b/aider/args.py index f14a8acec..43e03702e 100644 --- a/aider/args.py +++ b/aider/args.py @@ -410,6 +410,12 @@ def get_parser(default_config_files, git_root): default=True, help="Enable/disable adding .aider* to .gitignore (default: True)", ) + group.add_argument( + "--add-gitignore-files", + action=argparse.BooleanOptionalAction, + default=False, + help="Enable/disable the addition of files listed in .gitignore to Aider's editing scope.", + ) default_aiderignore_file = ( os.path.join(git_root, ".aiderignore") if git_root else ".aiderignore" ) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 10ad76efe..b824e9286 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -302,6 +302,7 @@ class Coder: io, repo=None, fnames=None, + add_gitignore_files=False, read_only_fnames=None, show_diffs=False, auto_commits=True, @@ -389,6 +390,7 @@ class Coder: self.verbose = verbose self.abs_fnames = set() self.abs_read_only_fnames = set() + self.add_gitignore_files = add_gitignore_files if cur_messages: self.cur_messages = cur_messages @@ -446,7 +448,7 @@ class Coder: for fname in fnames: fname = Path(fname) - if self.repo and self.repo.git_ignored_file(fname): + if self.repo and self.repo.git_ignored_file(fname) and not self.add_gitignore_files: self.io.tool_warning(f"Skipping {fname} that matches gitignore spec.") continue diff --git a/aider/commands.py b/aider/commands.py index aaf6d7ddd..b1fe05d39 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -844,7 +844,11 @@ class Commands: ) continue - if self.coder.repo and self.coder.repo.git_ignored_file(matched_file): + if ( + self.coder.repo + and self.coder.repo.git_ignored_file(matched_file) + and not self.coder.add_gitignore_files + ): self.io.tool_error(f"Can't add {matched_file} which is in gitignore") continue diff --git a/aider/io.py b/aider/io.py index 79f0b0c53..f8cf78cd3 100644 --- a/aider/io.py +++ b/aider/io.py @@ -1001,7 +1001,11 @@ class InputOutput: self.console.print(*messages, style=style) def get_assistant_mdstream(self): - mdargs = dict(style=self.assistant_output_color, code_theme=self.code_theme) + mdargs = dict( + style=self.assistant_output_color, + code_theme=self.code_theme, + inline_code_lexer="text", + ) mdStream = MarkdownStream(mdargs=mdargs) return mdStream diff --git a/aider/main.py b/aider/main.py index ec843c189..2156ddc9a 100644 --- a/aider/main.py +++ b/aider/main.py @@ -997,6 +997,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F detect_urls=args.detect_urls, auto_copy_context=args.copy_paste, auto_accept_architect=args.auto_accept_architect, + add_gitignore_files=args.add_gitignore_files, ) except UnknownEditFormat as err: io.tool_error(str(err)) diff --git a/aider/resources/model-settings.yml b/aider/resources/model-settings.yml index 6eb647e18..da4bf5f9e 100644 --- a/aider/resources/model-settings.yml +++ b/aider/resources/model-settings.yml @@ -978,16 +978,16 @@ - name: vertex_ai/gemini-2.5-pro-exp-03-25 edit_format: diff-fenced use_repo_map: true - weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + weak_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 overeager: true - editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + editor_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 - name: vertex_ai/gemini-2.5-pro-preview-03-25 edit_format: diff-fenced use_repo_map: true - weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + weak_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 overeager: true - editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + editor_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 - name: openrouter/openrouter/quasar-alpha use_repo_map: true @@ -1382,7 +1382,7 @@ use_repo_map: true accepts_settings: ["reasoning_effort", "thinking_tokens"] -- name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 +- name: vertex_ai/gemini-2.5-flash-preview-04-17 edit_format: diff use_repo_map: true accepts_settings: ["reasoning_effort", "thinking_tokens"] @@ -1409,16 +1409,16 @@ - name: vertex_ai/gemini-2.5-pro-preview-05-06 edit_format: diff-fenced use_repo_map: true - weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + weak_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 overeager: true - editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + editor_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 - name: vertex_ai/gemini-2.5-pro-preview-06-05 edit_format: diff-fenced use_repo_map: true - weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + weak_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 overeager: true - editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17 + editor_model_name: vertex_ai/gemini-2.5-flash-preview-04-17 accepts_settings: ["thinking_tokens"] - name: openrouter/google/gemini-2.5-pro-preview-05-06 diff --git a/aider/website/docs/config/options.md b/aider/website/docs/config/options.md index 88acb3deb..2cfc56ab7 100644 --- a/aider/website/docs/config/options.md +++ b/aider/website/docs/config/options.md @@ -388,6 +388,11 @@ Aliases: - `--gitignore` - `--no-gitignore` +### `--add-gitignore-files` +Enable/disable the addition of files listed in .gitignore to Aider's editing scope. +Default: False +Environment variable: `AIDER_ADD_GITIGNORE_FILES` + ### `--aiderignore AIDERIGNORE` Specify the aider ignore file (default: .aiderignore in git root) Default: .aiderignore diff --git a/tests/basic/test_main.py b/tests/basic/test_main.py index 1725360ad..d6863a204 100644 --- a/tests/basic/test_main.py +++ b/tests/basic/test_main.py @@ -152,6 +152,120 @@ class TestMain(TestCase): self.assertEqual("one\ntwo\n.aider*\n.env\n", gitignore.read_text()) del os.environ["GIT_CONFIG_GLOBAL"] + def test_command_line_gitignore_files_flag(self): + with GitTemporaryDirectory() as git_dir: + git_dir = Path(git_dir) + + # Create a .gitignore file + gitignore_file = git_dir / ".gitignore" + gitignore_file.write_text("ignored.txt\n") + + # Create an ignored file + ignored_file = git_dir / "ignored.txt" + ignored_file.write_text("This file should be ignored.") + + # Get the absolute path to the ignored file + abs_ignored_file = str(ignored_file.resolve()) + + # Test without the --add-gitignore-files flag (default: False) + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--exit", "--yes", abs_ignored_file], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + # Verify the ignored file is not in the chat + self.assertNotIn(abs_ignored_file, coder.abs_fnames) + + # Test with --add-gitignore-files set to True + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--add-gitignore-files", "--exit", "--yes", abs_ignored_file], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + # Verify the ignored file is in the chat + self.assertIn(abs_ignored_file, coder.abs_fnames) + + # Test with --add-gitignore-files set to False + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--no-add-gitignore-files", "--exit", "--yes", abs_ignored_file], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + # Verify the ignored file is not in the chat + self.assertNotIn(abs_ignored_file, coder.abs_fnames) + + def test_add_command_gitignore_files_flag(self): + with GitTemporaryDirectory() as git_dir: + git_dir = Path(git_dir) + + # Create a .gitignore file + gitignore_file = git_dir / ".gitignore" + gitignore_file.write_text("ignored.txt\n") + + # Create an ignored file + ignored_file = git_dir / "ignored.txt" + ignored_file.write_text("This file should be ignored.") + + # Get the absolute path to the ignored file + abs_ignored_file = str(ignored_file.resolve()) + rel_ignored_file = "ignored.txt" + + # Test without the --add-gitignore-files flag (default: False) + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--exit", "--yes"], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + + with patch.object(coder.io, "confirm_ask", return_value=True): + coder.commands.cmd_add(rel_ignored_file) + + # Verify the ignored file is not in the chat + self.assertNotIn(abs_ignored_file, coder.abs_fnames) + + # Test with --add-gitignore-files set to True + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--add-gitignore-files", "--exit", "--yes"], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + with patch.object(coder.io, "confirm_ask", return_value=True): + coder.commands.cmd_add(rel_ignored_file) + + # Verify the ignored file is in the chat + self.assertIn(abs_ignored_file, coder.abs_fnames) + + # Test with --add-gitignore-files set to False + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + coder = main( + ["--no-add-gitignore-files", "--exit", "--yes"], + input=DummyInput(), + output=DummyOutput(), + return_coder=True, + force_git_root=git_dir, + ) + + with patch.object(coder.io, "confirm_ask", return_value=True): + coder.commands.cmd_add(rel_ignored_file) + + # Verify the ignored file is not in the chat + self.assertNotIn(abs_ignored_file, coder.abs_fnames) + def test_main_args(self): with patch("aider.coders.Coder.create") as MockCoder: # --yes will just ok the git repo without blocking on input