---
phase: 22-log-flags
plan: 02
type: execute
wave: 2
depends_on: ["22-01"]
files_modified:
  - tests/test_log.py
autonomous: true

must_haves:
  truths:
    - "All 20 LOG requirements have E2E test coverage"
    - "Tests verify flag translation produces correct sl arguments"
    - "Tests verify output format for --name-only, --name-status"
    - "Tests verify warning messages for unsupported -S/-G flags"
  artifacts:
    - path: "tests/test_log.py"
      provides: "E2E tests for all log flag requirements"
      min_lines: 300
  key_links:
    - from: "tests/test_log.py"
      to: "cmd_log.py"
      via: "run_gitsl calling handle()"
      pattern: "run_gitsl.*log"
---

<objective>
Create comprehensive E2E tests for all git log flag translations.

Purpose: Validate that all 20 LOG requirements (LOG-01 through LOG-20) work correctly with real Sapling repositories.

Output: Extended tests/test_log.py with test classes covering all log flag requirements.
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/22-log-flags/22-RESEARCH.md
@.planning/phases/22-log-flags/22-01-SUMMARY.md
@tests/test_log.py
@tests/conftest.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add Tests for Display Flags (LOG-01, LOG-02, LOG-03)</name>
  <files>tests/test_log.py</files>
  <action>
Add new test class for display flags:

```python
class TestLogDisplayFlags:
    """Tests for LOG-01, LOG-02, LOG-03 display flags."""

    def test_graph_flag(self, sl_repo_with_commits: Path):
        """LOG-01: --graph shows ASCII commit graph."""
        result = run_gitsl(["log", "--graph", "-3"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Graph output contains special characters (|, *, /, \)
        # At minimum, should have some non-alphanumeric graph chars
        assert any(c in result.stdout for c in ['|', '*', '@'])

    def test_stat_flag(self, sl_repo_with_commit: Path):
        """LOG-02: --stat shows diffstat with each commit."""
        result = run_gitsl(["log", "--stat", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Stat output typically contains file changes like:
        # filename | N +++---
        assert '|' in result.stdout or 'file' in result.stdout.lower()

    def test_patch_flag(self, sl_repo_with_commit: Path):
        """LOG-03: --patch shows diff content."""
        result = run_gitsl(["log", "--patch", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Patch output contains diff markers
        assert '@@' in result.stdout or 'diff' in result.stdout.lower()

    def test_patch_short_flag(self, sl_repo_with_commit: Path):
        """LOG-03: -p is alias for --patch."""
        result = run_gitsl(["log", "-p", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert '@@' in result.stdout or 'diff' in result.stdout.lower()
```

Use existing fixtures: sl_repo_with_commit, sl_repo_with_commits from conftest.py.
  </action>
  <verify>`pytest tests/test_log.py::TestLogDisplayFlags -v`</verify>
  <done>Tests for LOG-01, LOG-02, LOG-03 pass</done>
</task>

<task type="auto">
  <name>Task 2: Add Tests for Filter Flags (LOG-04 through LOG-10)</name>
  <files>tests/test_log.py</files>
  <action>
Add test classes for filter and behavior flags:

```python
class TestLogFilterFlags:
    """Tests for LOG-04, LOG-05 filter flags."""

    def test_author_flag_equals_syntax(self, sl_repo_with_commits: Path):
        """LOG-04: --author=pattern filters by author."""
        # Get the author name from sl config
        result = run_gitsl(["log", "--author=test", "-1", "--oneline"], cwd=sl_repo_with_commits)
        # Should succeed (may or may not find matches depending on author)
        assert result.exit_code == 0

    def test_author_flag_space_syntax(self, sl_repo_with_commits: Path):
        """LOG-04: --author pattern with space syntax."""
        result = run_gitsl(["log", "--author", "test", "-1", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0

    def test_grep_flag_equals_syntax(self, sl_repo_with_commits: Path):
        """LOG-05: --grep=pattern searches commit messages."""
        # Search for "Commit" which is in the test commit messages
        result = run_gitsl(["log", "--grep=Commit", "-3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0

    def test_grep_flag_space_syntax(self, sl_repo_with_commits: Path):
        """LOG-05: --grep pattern with space syntax."""
        result = run_gitsl(["log", "--grep", "Commit", "-3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0


class TestLogBehaviorFlags:
    """Tests for LOG-06, LOG-07, LOG-08 behavior flags."""

    def test_no_merges_flag(self, sl_repo_with_commits: Path):
        """LOG-06: --no-merges excludes merge commits."""
        result = run_gitsl(["log", "--no-merges", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Should return some commits (test repo has no merges)
        assert result.stdout.strip() != ""

    def test_all_flag(self, sl_repo_with_commits: Path):
        """LOG-07: --all shows all branches."""
        result = run_gitsl(["log", "--all", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) >= 1

    def test_follow_flag(self, sl_repo_with_commit: Path):
        """LOG-08: --follow follows file renames."""
        # Create a file, commit, then check log with --follow
        result = run_gitsl(["log", "--follow", "-1", "--oneline", "test.txt"], cwd=sl_repo_with_commit)
        # May or may not work depending on if file exists; just check no crash
        # Exit code 0 or 1 is acceptable (file may not exist)
        assert result.exit_code in (0, 1)


class TestLogDateFlags:
    """Tests for LOG-09, LOG-10 date flags."""

    def test_since_flag_equals_syntax(self, sl_repo_with_commits: Path):
        """LOG-09: --since filters commits after date."""
        result = run_gitsl(["log", "--since=2020-01-01", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Should find commits (all test commits are recent)
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) >= 1

    def test_after_flag(self, sl_repo_with_commits: Path):
        """LOG-09: --after is alias for --since."""
        result = run_gitsl(["log", "--after=2020-01-01", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0

    def test_until_flag_equals_syntax(self, sl_repo_with_commits: Path):
        """LOG-10: --until filters commits before date."""
        result = run_gitsl(["log", "--until=2099-12-31", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) >= 1

    def test_before_flag(self, sl_repo_with_commits: Path):
        """LOG-10: --before is alias for --until."""
        result = run_gitsl(["log", "--before=2099-12-31", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0

    def test_since_and_until_combined(self, sl_repo_with_commits: Path):
        """LOG-09/LOG-10: Combined date range."""
        result = run_gitsl(["log", "--since=2020-01-01", "--until=2099-12-31", "-5", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
```
  </action>
  <verify>`pytest tests/test_log.py::TestLogFilterFlags tests/test_log.py::TestLogBehaviorFlags tests/test_log.py::TestLogDateFlags -v`</verify>
  <done>Tests for LOG-04 through LOG-10 pass</done>
</task>

<task type="auto">
  <name>Task 3: Add Tests for Output Format Flags (LOG-11 through LOG-14)</name>
  <files>tests/test_log.py</files>
  <action>
Add test class for output format flags:

```python
class TestLogOutputFormatFlags:
    """Tests for LOG-11, LOG-12, LOG-13, LOG-14 output format flags."""

    def test_name_only_flag(self, sl_repo_with_commit: Path):
        """LOG-11: --name-only shows only filenames."""
        result = run_gitsl(["log", "--name-only", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should show filename (test.txt is in the test commit)
        assert "test" in result.stdout.lower() or result.stdout.strip() != ""

    def test_name_status_flag(self, sl_repo_with_commit: Path):
        """LOG-12: --name-status shows status+filename."""
        result = run_gitsl(["log", "--name-status", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should show status indicators (A, M, D) and filename
        # At minimum, should have output
        assert result.stdout.strip() != ""

    def test_decorate_flag(self, sl_repo_with_commits: Path):
        """LOG-13: --decorate shows branch names."""
        result = run_gitsl(["log", "--decorate", "-1", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Should succeed; bookmark may or may not be present
        assert result.stdout.strip() != ""

    def test_pretty_oneline(self, sl_repo_with_commits: Path):
        """LOG-14: --pretty=oneline works like --oneline."""
        result1 = run_gitsl(["log", "--pretty=oneline", "-3"], cwd=sl_repo_with_commits)
        result2 = run_gitsl(["log", "--oneline", "-3"], cwd=sl_repo_with_commits)
        assert result1.exit_code == 0
        assert result2.exit_code == 0
        # Both should produce similar output format

    def test_pretty_short(self, sl_repo_with_commit: Path):
        """LOG-14: --pretty=short shows short format."""
        result = run_gitsl(["log", "--pretty=short", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() != ""

    def test_pretty_medium(self, sl_repo_with_commit: Path):
        """LOG-14: --pretty=medium shows medium format."""
        result = run_gitsl(["log", "--pretty=medium", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() != ""

    def test_pretty_full(self, sl_repo_with_commit: Path):
        """LOG-14: --pretty=full shows full format."""
        result = run_gitsl(["log", "--pretty=full", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() != ""

    def test_format_custom_hash(self, sl_repo_with_commit: Path):
        """LOG-14: --format=format:%h shows short hash."""
        result = run_gitsl(["log", "--format=format:%h", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should show hex hash
        output = result.stdout.strip()
        if output:
            assert all(c in "0123456789abcdef\n" for c in output)

    def test_format_custom_subject(self, sl_repo_with_commit: Path):
        """LOG-14: --format=format:%s shows subject."""
        result = run_gitsl(["log", "--format=format:%s", "-1"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert result.stdout.strip() != ""
```
  </action>
  <verify>`pytest tests/test_log.py::TestLogOutputFormatFlags -v`</verify>
  <done>Tests for LOG-11 through LOG-14 pass</done>
</task>

<task type="auto">
  <name>Task 4: Add Tests for Complex Flags and Warnings (LOG-15 through LOG-20)</name>
  <files>tests/test_log.py</files>
  <action>
Add test class for complex flags and document existing functionality:

```python
class TestLogComplexFlags:
    """Tests for LOG-15, LOG-16 complex flags."""

    def test_first_parent_flag(self, sl_repo_with_commits: Path):
        """LOG-15: --first-parent follows only first parent."""
        result = run_gitsl(["log", "--first-parent", "-3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Should produce some output
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) >= 1

    def test_reverse_flag(self, sl_repo_with_commits: Path):
        """LOG-16: --reverse shows commits in reverse order."""
        result = run_gitsl(["log", "--reverse", "-3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        # Should produce output (reversed or not, depending on implementation)
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) >= 1


class TestLogPickaxeWarnings:
    """Tests for LOG-17, LOG-18 pickaxe warnings."""

    def test_pickaxe_S_warns(self, sl_repo_with_commit: Path):
        """LOG-17: -S warns about unsupported pickaxe."""
        result = run_gitsl(["log", "-Ssearchterm", "-1"], cwd=sl_repo_with_commit)
        # Should succeed but warn
        assert result.exit_code == 0
        # Warning should be in stderr
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()

    def test_pickaxe_G_warns(self, sl_repo_with_commit: Path):
        """LOG-18: -G warns about unsupported regex pickaxe."""
        result = run_gitsl(["log", "-Gpattern", "-1"], cwd=sl_repo_with_commit)
        # Should succeed but warn
        assert result.exit_code == 0
        # Warning should be in stderr
        assert "not supported" in result.stderr.lower() or "warning" in result.stderr.lower()


class TestLogExistingFlags:
    """Tests documenting LOG-19, LOG-20 already implemented flags."""

    def test_max_count_long_form(self, sl_repo_with_commits: Path):
        """LOG-19: --max-count=N already implemented."""
        result = run_gitsl(["log", "--max-count=3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) == 3

    def test_n_flag_already_works(self, sl_repo_with_commits: Path):
        """LOG-19: -n N already implemented."""
        result = run_gitsl(["log", "-n", "3", "--oneline"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        lines = [l for l in result.stdout.strip().split("\n") if l]
        assert len(lines) == 3

    def test_oneline_already_works(self, sl_repo_with_commits: Path):
        """LOG-20: --oneline already implemented."""
        result = run_gitsl(["log", "--oneline", "-3"], cwd=sl_repo_with_commits)
        assert result.exit_code == 0
        lines = result.stdout.strip().split("\n")
        assert len(lines) == 3
        # Each line should be hash + subject
        for line in lines:
            parts = line.split(" ", 1)
            assert len(parts) == 2
```
  </action>
  <verify>`pytest tests/test_log.py::TestLogComplexFlags tests/test_log.py::TestLogPickaxeWarnings tests/test_log.py::TestLogExistingFlags -v`</verify>
  <done>Tests for LOG-15 through LOG-20 pass</done>
</task>

</tasks>

<verification>
1. `pytest tests/test_log.py -v` - all log tests pass
2. `pytest tests/test_log.py --tb=short` - no unexpected failures
3. Test count increased from ~15 tests to ~35+ tests
</verification>

<success_criteria>
1. All 20 LOG requirements have at least one test
2. All new tests pass with the implementation from 22-01
3. Existing tests still pass (no regressions)
4. Tests cover both success cases and warning cases
5. Test output is clean (no unexpected warnings)
</success_criteria>

<output>
After completion, create `.planning/phases/22-log-flags/22-02-SUMMARY.md`
</output>
