Get the git diff between the baseline SHA and the current working tree, including untracked (new) files. Uses a temporary copy of the git index (GIT_INDEX_FILE) so the user's real index is never modified. The temp index gets intent-to-add entries for untracked files, making the
(cwd, baseline_sha, full_context=False, paths=None, untracked_paths=None)
| 389 | |
| 390 | |
| 391 | def get_git_diff(cwd, baseline_sha, full_context=False, paths=None, untracked_paths=None): |
| 392 | """ |
| 393 | Get the git diff between the baseline SHA and the current working tree, |
| 394 | including untracked (new) files. |
| 395 | |
| 396 | Uses a temporary copy of the git index (GIT_INDEX_FILE) so the user's |
| 397 | real index is never modified. The temp index gets intent-to-add entries |
| 398 | for untracked files, making them visible in the diff output. Cleanup |
| 399 | is just deleting the temp file in a finally block. |
| 400 | |
| 401 | If `paths` is given, the diff is restricted to those paths (relative to |
| 402 | cwd; absolute paths are converted, paths outside cwd are dropped). |
| 403 | `untracked_paths` (repo-root-relative) is forwarded to _temp_index so it |
| 404 | can add only those files instead of scanning the whole worktree. |
| 405 | """ |
| 406 | pathspec = _diff_pathspec(cwd, paths) |
| 407 | if paths and not pathspec: |
| 408 | # Caller restricted to specific paths but none are inside this repo |
| 409 | # (e.g. only ~/.claude/... edits). Returning "" flows to skip(6); an |
| 410 | # empty pathspec would mean an UNRESTRICTED diff — the bug this whole |
| 411 | # change exists to fix. |
| 412 | return "" |
| 413 | |
| 414 | cmd = [*GIT_CMD, "diff", "--no-color", "--no-ext-diff", baseline_sha] + (["--unified=99999"] if full_context else []) + pathspec |
| 415 | try: |
| 416 | with _temp_index(cwd, untracked_paths) as env: |
| 417 | # env is None when no index could be found (bare repo / not a |
| 418 | # repo) — diff still runs, just without untracked-file support. |
| 419 | result = subprocess.run(cmd, cwd=cwd, capture_output=True, timeout=30, env=env) |
| 420 | if result.returncode != 0: |
| 421 | debug_log(f"git diff failed: {result.stderr[:200].decode('utf-8', errors='replace')}") |
| 422 | return None |
| 423 | # Decode with errors='replace' so binary diffs don't crash |
| 424 | return result.stdout.decode("utf-8", errors="replace") |
| 425 | except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e: |
| 426 | debug_log(f"git diff error: {e}") |
| 427 | return None |
| 428 | |
| 429 | |
| 430 | # Source file extensions worth reviewing for security |
no test coverage detected