diff --git a/sdk/typescript/_bundled_plugin/scripts/generate_in_scope_files.py b/sdk/typescript/_bundled_plugin/scripts/generate_in_scope_files.py index d3239711..6449bf74 100644 --- a/sdk/typescript/_bundled_plugin/scripts/generate_in_scope_files.py +++ b/sdk/typescript/_bundled_plugin/scripts/generate_in_scope_files.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse +import os import subprocess import sys import tempfile @@ -105,6 +106,38 @@ def generate_in_scope_files(repository: Path, scope: str, output: Path) -> int: return write_inventory(output, rows) +def committed_changed_paths(repository: Path, base: str, head: str) -> list[tuple[Path, str]]: + result = subprocess.run( + [ + "git", + "-C", + str(repository), + "diff", + "--raw", + "-z", + "--diff-filter=ACMRD", + f"{base}..{head}", + ], + capture_output=True, + check=True, + ) + fields = result.stdout.split(b"\0") + changed: list[tuple[Path, str]] = [] + index = 0 + while index < len(fields) - 1: + metadata = fields[index].split() + status = chr(metadata[-1][0]) + index += 1 + if status in {"C", "R"}: + index += 1 + path = os.fsdecode(fields[index]) + index += 1 + selected_mode = metadata[0].removeprefix(b":") if status == "D" else metadata[1] + if selected_mode != b"120000": + changed.append((repository / path, status)) + return changed + + def generate_diff_in_scope_files( repository: Path, base: str, @@ -125,7 +158,11 @@ def generate_diff_in_scope_files( rows: list[bytes] = [] try: - changed = git_changed_paths(repository, base, head, mode) + changed = ( + committed_changed_paths(repository, base, head) + if mode == "revisions" + else git_changed_paths(repository, base, head, mode) + ) eligible = [ (path, status) for path, status in changed diff --git a/sdk/typescript/tests-ts/compact-diff-scan.test.ts b/sdk/typescript/tests-ts/compact-diff-scan.test.ts index 2543c7c5..02982272 100644 --- a/sdk/typescript/tests-ts/compact-diff-scan.test.ts +++ b/sdk/typescript/tests-ts/compact-diff-scan.test.ts @@ -244,6 +244,67 @@ describe("compact diff scan", () => { ]); }); + test("omits committed symlinks from the revision inventory", () => { + const { root, repository } = createRepository(); + writeSource(repository, "src/handler.py", "value = 1\n"); + writeSource(repository, "src/deleted-link.py", "handler.py"); + git(repository, "add", "."); + const deletedLink = git(repository, "hash-object", "src/deleted-link.py"); + git( + repository, + "update-index", + "--cacheinfo", + `120000,${deletedLink},src/deleted-link.py`, + ); + git(repository, "commit", "-qm", "base"); + const base = git(repository, "rev-parse", "HEAD"); + + rmSync(join(repository, "src", "deleted-link.py")); + writeSource(repository, "src/handler.py", "value = 2\n"); + writeSource(repository, "src/丁.py", "value = 3\n"); + writeSource(repository, "src/added-link.py", "handler.py"); + git(repository, "add", "."); + const addedLink = git(repository, "hash-object", "src/added-link.py"); + git( + repository, + "update-index", + "--cacheinfo", + `120000,${addedLink},src/added-link.py`, + ); + git(repository, "commit", "-qm", "selected changes"); + const head = git(repository, "rev-parse", "HEAD"); + const output = join(root, "in-scope.txt"); + + const executable = Bun.which("python3") ?? Bun.which("python"); + expect(executable).not.toBeNull(); + const result = spawnSync( + executable!, + [ + "-B", + "-c", + "import locale, runpy, sys; locale.setlocale(locale.LC_CTYPE, 'C'); runpy.run_path(sys.argv.pop(1), run_name='__main__')", + join(PLUGIN_ROOT, "scripts", "generate_in_scope_files.py"), + "--repo", + repository, + "--scope", + ".", + "--diff-base", + base, + "--diff-head", + head, + "--out", + output, + ], + { encoding: "utf8", env: { ...process.env, PYTHONUTF8: "0" } }, + ); + + expect(result.status, result.stderr).toBe(0); + expect(readFileSync(output, "utf8").split("\n").filter(Boolean)).toEqual([ + "src/handler.py", + "src/丁.py", + ]); + }); + test("keeps staged, unstaged, and untracked working-tree inputs aligned", () => { const { root, repository } = createRepository(); writeSource(repository, "src/handler.py", "value = 1\n");