diff --git a/aider_gitea/__init__.py b/aider_gitea/__init__.py index 9fe3f23..a378648 100644 --- a/aider_gitea/__init__.py +++ b/aider_gitea/__init__.py @@ -467,6 +467,109 @@ def get_diff(cwd: Path, base_branch: str, current_branch: str) -> str: return result.stdout.strip() +def contains_only_ruff_changes( + cwd: Path, + base_branch: str, + current_branch: str, +) -> bool: + """Check if the branch contains only ruff formatting changes. + + Args: + cwd: The current working directory (repository path). + base_branch: The name of the base branch to compare against. + current_branch: The name of the current branch to check. + + Returns: + True if the branch contains only ruff formatting changes, False otherwise. + """ + # Get all commit messages to check if they're only ruff-related + commit_messages = get_commit_messages(cwd, base_branch, current_branch) + + # If no commits, then no changes at all + if not commit_messages: + return True + + # Check if all commits are ruff-related + ruff_related_patterns = [ + 'ruff', + 'format', + 'lint', + 'auto-fix', + 'autofix', + 'code style', + 'formatting', + ] + + all_ruff_commits = True + for message in commit_messages: + message_lower = message.lower() + # Skip empty messages + if not message.strip(): + continue + # Check if this commit message indicates ruff/formatting changes + is_ruff_commit = any( + pattern in message_lower for pattern in ruff_related_patterns + ) + if not is_ruff_commit: + all_ruff_commits = False + break + + if not all_ruff_commits: + return False + + # Additional check: get the actual diff and analyze it + # This helps catch cases where commit messages might be misleading + try: + diff_result = subprocess.run( + ['git', 'diff', f'{base_branch}..{current_branch}'], + check=True, + cwd=cwd, + capture_output=True, + text=True, + ) + diff_content = diff_result.stdout + + # If no diff content, then only ruff changes + if not diff_content.strip(): + return True + + # Analyze the diff to see if it contains only whitespace/formatting changes + lines = diff_content.split('\n') + substantive_changes = 0 + + for line in lines: + # Skip diff headers and file markers + if ( + line.startswith('diff --git') + or line.startswith('index ') + or line.startswith('+++') + or line.startswith('---') + ): + continue + # Skip context lines (no + or -) + if line.startswith(' ') or not line.strip(): + continue + # Count added/removed lines + if line.startswith('+') or line.startswith('-'): + # Remove the +/- prefix and check if it's substantive + content = line[1:].strip() + # Skip empty lines + if not content: + continue + # This is a substantive change if it's not just whitespace + if content and not content.isspace(): + substantive_changes += 1 + + # If we have very few substantive changes and all commits are ruff-related, + # it's likely only formatting changes + return substantive_changes <= 2 # Allow for minimal formatting adjustments + + except subprocess.CalledProcessError: + logger.exception('Failed to get diff for ruff-only check') + # If we can't get the diff, fall back to commit message analysis + return all_ruff_commits + + def push_changes( repository_config: RepositoryConfig, cwd: Path, @@ -480,6 +583,14 @@ def push_changes( logger.info('No commits made on branch %s, skipping push', branch_name) return IssueResolution(False) + # Check if the branch contains only ruff formatting changes + if contains_only_ruff_changes(cwd, repository_config.base_branch, branch_name): + logger.info( + 'Branch %s contains only ruff formatting changes, skipping MR creation to avoid ruff-only PRs', + branch_name, + ) + return IssueResolution(False) + # Get commit messages for PR description commit_messages = get_commit_messages( cwd, diff --git a/test/test_claude_code_integration.py b/test/test_claude_code_integration.py index 9229225..ad83669 100644 --- a/test/test_claude_code_integration.py +++ b/test/test_claude_code_integration.py @@ -69,9 +69,10 @@ class TestClaudeCodeIntegration: 'claude', '-p', '--output-format', - 'json', - '--max-turns', - '10', + 'stream-json', + '--debug', + '--verbose', + '--dangerously-skip-permissions', issue, ] assert cmd == expected @@ -84,9 +85,10 @@ class TestClaudeCodeIntegration: 'claude', '-p', '--output-format', - 'json', - '--max-turns', - '10', + 'stream-json', + '--debug', + '--verbose', + '--dangerously-skip-permissions', '--model', 'claude-3-sonnet', issue, diff --git a/test/test_contains_only_ruff_changes.py b/test/test_contains_only_ruff_changes.py new file mode 100644 index 0000000..274752c --- /dev/null +++ b/test/test_contains_only_ruff_changes.py @@ -0,0 +1,189 @@ +import subprocess +import tempfile +from pathlib import Path + +from aider_gitea import contains_only_ruff_changes + + +class TestContainsOnlyRuffChanges: + """Test the contains_only_ruff_changes function.""" + + def setup_test_repo(self) -> Path: + """Create a test git repository.""" + temp_dir = Path(tempfile.mkdtemp()) + + # Initialize git repo + subprocess.run(['git', 'init'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'config', 'user.name', 'Test User'], + cwd=temp_dir, + check=True, + ) + subprocess.run( + ['git', 'config', 'user.email', 'test@example.com'], + cwd=temp_dir, + check=True, + ) + + # Create initial content and commit + test_file = temp_dir / 'test.py' + test_file.write_text('def hello():\n print("hello")\n') + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'commit', '-m', 'Initial commit'], + cwd=temp_dir, + check=True, + ) + + # Rename master to main for consistency + subprocess.run( + ['git', 'branch', '-m', 'master', 'main'], + cwd=temp_dir, + check=True, + ) + + return temp_dir + + def test_no_commits_returns_true(self): + """Test that branches with no commits return True.""" + temp_dir = self.setup_test_repo() + + # Create a new branch but don't commit anything + subprocess.run( + ['git', 'checkout', '-b', 'test-branch'], + cwd=temp_dir, + check=True, + ) + + result = contains_only_ruff_changes(temp_dir, 'main', 'test-branch') + assert result is True + + def test_only_ruff_commits_returns_true(self): + """Test that branches with only ruff-related commits return True.""" + temp_dir = self.setup_test_repo() + + # Create a new branch and make ruff-related commits + subprocess.run( + ['git', 'checkout', '-b', 'test-branch'], + cwd=temp_dir, + check=True, + ) + + # Modify the file with formatting changes + test_file = temp_dir / 'test.py' + test_file.write_text('def hello():\n print("hello")\n\n') # Added newline + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'commit', '-m', 'ruff format changes'], + cwd=temp_dir, + check=True, + ) + + # Another ruff commit + test_file.write_text( + 'def hello():\n print("hello")\n', + ) # Removed newline again + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'commit', '-m', 'auto-fix lint issues'], + cwd=temp_dir, + check=True, + ) + + result = contains_only_ruff_changes(temp_dir, 'main', 'test-branch') + assert result is True + + def test_substantive_changes_returns_false(self): + """Test that branches with substantive changes return False.""" + temp_dir = self.setup_test_repo() + + # Create a new branch and make substantive changes + subprocess.run( + ['git', 'checkout', '-b', 'test-branch'], + cwd=temp_dir, + check=True, + ) + + # Add a new function (substantive change) + test_file = temp_dir / 'test.py' + test_file.write_text( + 'def hello():\n print("hello")\n\ndef goodbye():\n print("goodbye")\n', + ) + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'commit', '-m', 'Add goodbye function'], + cwd=temp_dir, + check=True, + ) + + result = contains_only_ruff_changes(temp_dir, 'main', 'test-branch') + assert result is False + + def test_mixed_commits_returns_false(self): + """Test that branches with both ruff and substantive commits return False.""" + temp_dir = self.setup_test_repo() + + # Create a new branch + subprocess.run( + ['git', 'checkout', '-b', 'test-branch'], + cwd=temp_dir, + check=True, + ) + + # First, a substantive change + test_file = temp_dir / 'test.py' + test_file.write_text( + 'def hello():\n print("hello world")\n', + ) # Changed string + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'commit', '-m', 'Update greeting message'], + cwd=temp_dir, + check=True, + ) + + # Then, a ruff change + test_file.write_text( + 'def hello():\n print("hello world")\n\n', + ) # Added newline + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run(['git', 'commit', '-m', 'ruff format'], cwd=temp_dir, check=True) + + result = contains_only_ruff_changes(temp_dir, 'main', 'test-branch') + assert result is False + + def test_ruff_keywords_in_commit_messages(self): + """Test various ruff-related keywords in commit messages.""" + temp_dir = self.setup_test_repo() + + ruff_messages = [ + 'ruff format', + 'Ruff after aider', + 'auto-fix lint issues', + 'code style formatting', + 'Apply formatting changes', + 'Lint fixes', + ] + + for i, message in enumerate(ruff_messages): + # Create a branch for each test + branch_name = f'test-branch-{i}' + subprocess.run(['git', 'checkout', 'main'], cwd=temp_dir, check=True) + subprocess.run( + ['git', 'checkout', '-b', branch_name], + cwd=temp_dir, + check=True, + ) + + # Make a minor formatting change that's always different + test_file = temp_dir / 'test.py' + content = test_file.read_text() + # Add a unique comment to make each change different + content += f'# formatting change {i}\n' + test_file.write_text(content) + + subprocess.run(['git', 'add', '.'], cwd=temp_dir, check=True) + subprocess.run(['git', 'commit', '-m', message], cwd=temp_dir, check=True) + + result = contains_only_ruff_changes(temp_dir, 'main', branch_name) + assert result is True, f'Failed for message: {message}'