---
phase: 15-direct-pass-through
plan: 02
type: execute
wave: 2
depends_on: ["15-01"]
files_modified:
  - tests/test_show.py
  - tests/test_blame.py
  - tests/test_rm.py
  - tests/test_mv.py
  - tests/test_clone.py
  - tests/test_grep.py
autonomous: true

must_haves:
  truths:
    - "git show tests pass in Sapling repository"
    - "git blame tests pass in Sapling repository"
    - "git rm tests pass in Sapling repository"
    - "git mv tests pass in Sapling repository"
    - "git clone tests pass with local repository"
    - "git grep tests pass in Sapling repository"
  artifacts:
    - path: "tests/test_show.py"
      provides: "E2E tests for SHOW-01, SHOW-02"
      contains: "TestShowBasic, TestShowCommit"
    - path: "tests/test_blame.py"
      provides: "E2E tests for BLAME-01, BLAME-02"
      contains: "TestBlameBasic, TestBlameFlags"
    - path: "tests/test_rm.py"
      provides: "E2E tests for RM-01, RM-02, RM-03"
      contains: "TestRmBasic, TestRmForce, TestRmRecursive"
    - path: "tests/test_mv.py"
      provides: "E2E tests for MV-01, MV-02"
      contains: "TestMvBasic, TestMvForce"
    - path: "tests/test_clone.py"
      provides: "E2E tests for CLONE-01, CLONE-02"
      contains: "TestCloneUrl, TestCloneDir"
    - path: "tests/test_grep.py"
      provides: "E2E tests for GREP-01, GREP-02"
      contains: "TestGrepBasic, TestGrepFlags"
  key_links:
    - from: "tests/test_*.py"
      to: "conftest.run_gitsl"
      via: "import and function call"
      pattern: "run_gitsl\\("
    - from: "tests/test_*.py"
      to: "sl_repo_with_commit fixture"
      via: "pytest fixture injection"
      pattern: "sl_repo_with_commit"
---

<objective>
Create E2E tests for all 6 direct pass-through commands, validating handler behavior in Sapling repositories.

Purpose: Ensure all 13 requirements (SHOW-01/02, BLAME-01/02, RM-01/02/03, MV-01/02, CLONE-01/02, GREP-01/02) are tested.
Output: 6 test files following test_diff.py pattern, all tests passing.
</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/phases/15-direct-pass-through/15-RESEARCH.md
@.planning/phases/15-direct-pass-through/15-01-SUMMARY.md

# Reference existing test patterns
@tests/test_diff.py
@tests/conftest.py
@tests/helpers/commands.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create test files for show, blame, rm commands</name>
  <files>
    tests/test_show.py
    tests/test_blame.py
    tests/test_rm.py
  </files>
  <action>
Create E2E test files following test_diff.py pattern.

**tests/test_show.py** (SHOW-01, SHOW-02):
```python
"""E2E tests for git show command (SHOW-01, SHOW-02)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl
from helpers.commands import run_command


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.show,
]


class TestShowBasic:
    """SHOW-01: git show translates to sl show."""

    def test_show_current_commit(self, sl_repo_with_commit: Path):
        """git show displays current commit."""
        result = run_gitsl(["show"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should show commit info or diff output
        assert len(result.stdout) > 0


class TestShowCommit:
    """SHOW-02: git show <commit> shows specified commit."""

    def test_show_with_stat_flag(self, sl_repo_with_commit: Path):
        """git show --stat displays file statistics."""
        result = run_gitsl(["show", "--stat"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
```

**tests/test_blame.py** (BLAME-01, BLAME-02):
```python
"""E2E tests for git blame command (BLAME-01, BLAME-02)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.blame,
]


class TestBlameBasic:
    """BLAME-01: git blame <file> translates to sl annotate <file>."""

    def test_blame_file(self, sl_repo_with_commit: Path):
        """git blame shows per-line annotations."""
        result = run_gitsl(["blame", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Should show annotated content
        assert len(result.stdout) > 0


class TestBlameFlags:
    """BLAME-02: git blame passes through common flags."""

    def test_blame_ignore_whitespace(self, sl_repo_with_commit: Path):
        """git blame -w ignores whitespace changes."""
        result = run_gitsl(["blame", "-w", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
```

**tests/test_rm.py** (RM-01, RM-02, RM-03):
```python
"""E2E tests for git rm command (RM-01, RM-02, RM-03)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl
from helpers.commands import run_command


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.rm,
]


class TestRmBasic:
    """RM-01: git rm <files> translates to sl remove <files>."""

    def test_rm_tracked_file(self, sl_repo_with_commit: Path):
        """git rm removes tracked file."""
        result = run_gitsl(["rm", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Verify file is marked for removal
        status = run_command(["sl", "status"], cwd=sl_repo_with_commit)
        assert "R README.md" in status.stdout


class TestRmForce:
    """RM-02: git rm -f translates to sl remove -f."""

    def test_rm_force_modified_file(self, sl_repo_with_commit: Path):
        """git rm -f forces removal of modified file."""
        # Modify the file first
        readme = sl_repo_with_commit / "README.md"
        readme.write_text("Modified content\n")

        result = run_gitsl(["rm", "-f", "README.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0


class TestRmRecursive:
    """RM-03: git rm -r translates to sl remove (recursive by default)."""

    def test_rm_recursive_directory(self, sl_repo_with_commit: Path):
        """git rm -r removes directory recursively."""
        # Create a subdirectory with files
        subdir = sl_repo_with_commit / "subdir"
        subdir.mkdir()
        subfile = subdir / "file.txt"
        subfile.write_text("content\n")
        run_command(["sl", "add", "subdir/file.txt"], cwd=sl_repo_with_commit)
        run_command(["sl", "commit", "-m", "Add subdir"], cwd=sl_repo_with_commit)

        result = run_gitsl(["rm", "-r", "subdir"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
```

All test files should:
- Use sl_repo_with_commit fixture (NOT git_repo)
- Have pytestmark with skipif for sl availability and command-specific marker
- Follow existing test naming conventions
  </action>
  <verify>
    python -m py_compile tests/test_show.py tests/test_blame.py tests/test_rm.py
  </verify>
  <done>
    3 test files created for show, blame, rm commands
  </done>
</task>

<task type="auto">
  <name>Task 2: Create test files for mv, clone, grep commands</name>
  <files>
    tests/test_mv.py
    tests/test_clone.py
    tests/test_grep.py
  </files>
  <action>
Create remaining E2E test files.

**tests/test_mv.py** (MV-01, MV-02):
```python
"""E2E tests for git mv command (MV-01, MV-02)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl
from helpers.commands import run_command


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.mv,
]


class TestMvBasic:
    """MV-01: git mv <src> <dst> translates to sl rename <src> <dst>."""

    def test_mv_rename_file(self, sl_repo_with_commit: Path):
        """git mv renames a file."""
        result = run_gitsl(["mv", "README.md", "RENAMED.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

        # Verify rename occurred
        assert not (sl_repo_with_commit / "README.md").exists()
        assert (sl_repo_with_commit / "RENAMED.md").exists()


class TestMvForce:
    """MV-02: git mv -f translates to sl rename -f."""

    def test_mv_force_overwrite(self, sl_repo_with_commit: Path):
        """git mv -f can overwrite existing file."""
        # Create destination file
        dest = sl_repo_with_commit / "dest.md"
        dest.write_text("destination\n")
        run_command(["sl", "add", "dest.md"], cwd=sl_repo_with_commit)
        run_command(["sl", "commit", "-m", "Add dest"], cwd=sl_repo_with_commit)

        result = run_gitsl(["mv", "-f", "README.md", "dest.md"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
```

**tests/test_clone.py** (CLONE-01, CLONE-02):
IMPORTANT: Clone tests use tmp_path directly, NOT sl_repo fixture. Clone creates new repos.

```python
"""E2E tests for git clone command (CLONE-01, CLONE-02)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl
from helpers.commands import run_command


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.clone,
]


class TestCloneUrl:
    """CLONE-01: git clone <url> translates to sl clone <url>."""

    def test_clone_local_repo(self, sl_repo_with_commit: Path, tmp_path: Path):
        """git clone can clone a local Sapling repository."""
        dest = tmp_path / "cloned"
        result = run_gitsl(["clone", str(sl_repo_with_commit), str(dest)], cwd=tmp_path)
        assert result.exit_code == 0
        assert dest.exists()
        assert (dest / ".sl").exists() or (dest / ".hg").exists()


class TestCloneDir:
    """CLONE-02: git clone <url> <dir> translates to sl clone <url> <dir>."""

    def test_clone_with_destination_name(self, sl_repo_with_commit: Path, tmp_path: Path):
        """git clone can specify destination directory name."""
        result = run_gitsl(["clone", str(sl_repo_with_commit), "my-clone"], cwd=tmp_path)
        assert result.exit_code == 0
        assert (tmp_path / "my-clone").exists()
```

**tests/test_grep.py** (GREP-01, GREP-02):
```python
"""E2E tests for git grep command (GREP-01, GREP-02)."""

import shutil
from pathlib import Path

import pytest

from conftest import run_gitsl


sl_available = shutil.which("sl") is not None
pytestmark = [
    pytest.mark.skipif(not sl_available, reason="Sapling (sl) not installed"),
    pytest.mark.grep,
]


class TestGrepBasic:
    """GREP-01: git grep <pattern> translates to sl grep <pattern>."""

    def test_grep_finds_pattern(self, sl_repo_with_commit: Path):
        """git grep finds pattern in tracked files."""
        result = run_gitsl(["grep", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # README.md contains "# Test Repository"
        assert "README" in result.stdout or "Test" in result.stdout

    def test_grep_no_match_returns_nonzero(self, sl_repo_with_commit: Path):
        """git grep with no matches returns non-zero."""
        result = run_gitsl(["grep", "nonexistent_pattern_xyz"], cwd=sl_repo_with_commit)
        # sl grep returns 1 when no matches (same as git grep)
        assert result.exit_code != 0 or result.stdout == ""


class TestGrepFlags:
    """GREP-02: git grep passes through common flags."""

    def test_grep_line_numbers(self, sl_repo_with_commit: Path):
        """git grep -n shows line numbers."""
        result = run_gitsl(["grep", "-n", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        # Output should contain line number (e.g., "README.md:1:...")
        assert ":" in result.stdout

    def test_grep_case_insensitive(self, sl_repo_with_commit: Path):
        """git grep -i performs case-insensitive search."""
        result = run_gitsl(["grep", "-i", "test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0

    def test_grep_files_only(self, sl_repo_with_commit: Path):
        """git grep -l shows only file names."""
        result = run_gitsl(["grep", "-l", "Test"], cwd=sl_repo_with_commit)
        assert result.exit_code == 0
        assert "README" in result.stdout
```
  </action>
  <verify>
    python -m py_compile tests/test_mv.py tests/test_clone.py tests/test_grep.py
  </verify>
  <done>
    3 test files created for mv, clone, grep commands
  </done>
</task>

<task type="auto">
  <name>Task 3: Run all Phase 15 tests</name>
  <files>N/A - verification only</files>
  <action>
Run pytest for all 6 new test files to validate functionality.

Execute:
  pytest tests/test_show.py tests/test_blame.py tests/test_rm.py tests/test_mv.py tests/test_clone.py tests/test_grep.py -v

If any tests fail:
1. Check if it's a fixture issue (using git_repo vs sl_repo)
2. Check if the handler command mapping is correct
3. Check if flags are being passed correctly
4. Fix the specific failing test

Do NOT modify handlers created in Plan 01 unless there's a genuine bug.
  </action>
  <verify>
    pytest tests/test_show.py tests/test_blame.py tests/test_rm.py tests/test_mv.py tests/test_clone.py tests/test_grep.py -v
    All tests should pass
  </verify>
  <done>
    All Phase 15 tests pass, validating 13 requirements
  </done>
</task>

</tasks>

<verification>
1. All 6 test files exist and are syntactically valid
2. pytest discovers all tests correctly
3. All tests pass when sl is available
4. Tests skip gracefully when sl is not installed
5. pytest --collect-only shows correct test count
</verification>

<success_criteria>
- 6 new test files created following test_diff.py pattern
- All tests pass: pytest tests/test_show.py tests/test_blame.py tests/test_rm.py tests/test_mv.py tests/test_clone.py tests/test_grep.py
- Requirements SHOW-01/02, BLAME-01/02, RM-01/02/03, MV-01/02, CLONE-01/02, GREP-01/02 validated by tests
</success_criteria>

<output>
After completion, create `.planning/phases/15-direct-pass-through/15-02-SUMMARY.md`
</output>
