Skip to content

test(git): reproduce upstream 810d424 and ae9721e on macOS - #56

Merged
axisrow merged 1 commit into
mainfrom
verify/git-hardening
Oct 12, 2026
Merged

axisrow merged 1 commit into
mainfrom
verify/git-hardening

Conversation

@axisrow

@axisrow axisrow commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Batch 2: reproduce upstream git fixes on macOS (810d424, ae9721e)

TDD verification per the /fix pipeline: red on the pre-fix tree → green on main under both runners (bun test + vitest/Node) → mutation check (StrykerJS) → lint. Follows #55 (tooling + pilot). Depends on its Stryker config; branch is stacked on verify/tooling with --base main.

Upstream CI only runs Linux; both fixes are security-relevant (GHSA-rf4f-gc2h-c9vp; quadratic parser input from repository config), so the wave proves them on macOS.

Fix Red fact (pre-fix tree) Green bun Green vitest/Node Mutation Commit
810d424 — git ignores repo-controlled commands core.fsmonitor hook from repo config really executed during a status call (marker file created) @ 31983de4 20/20 60/60 (with ae9721e suite) git-hardening.ts 96.24% (178 killed + 1 timeout / 186) 007fd6e
ae9721e — linear-time git parsers parseRemoteUrl took 5931–7073ms (limit 1000ms) on a 100k-slash remote URL @ e8ef3cb 40/40 60/60 (with 810d424 suite) git-remote.ts 93.20% (137 / 147) 2b38802

Survivors triage

All 17 surviving mutants across the two files were triaged as equivalent, each with a concrete reason:

git-hardening.ts (7)

  • relative === '' || (2 mutants): implied by the right-hand side — '' never starts with .. and is never absolute.
  • match?.[3] optional chaining: covered by the short-circuit !program (match is null only when program is undefined).
  • Loop bound mutants (<→<=, +→-) at the parse loop: out-of-range iterations produce undefined scope/keyValue and are eaten by the existing guard.
  • parseInt(env.GIT_CONFIG_COUNT ?? '0') → ?? '': both yield NaN → start 0.
  • existing < 0 → existing <= 0: identical at existing === 0 (both start at 0).

git-remote.ts (10)

  • Empty-input guard (2 mutants): both paths already return null (SSH regex cannot match, new URL('') throws).
  • Double filter(Boolean) (2 mutants): the second filter already drops what the first would.
  • slice() before sort(): split() already returns a fresh array.
  • sort comparator mutants (2): sorting cannot change the find() outcome — overlapping remote prefixes cannot both match a tracking ref (a prefix match requires the slash position to differ).
  • upstreamRef === remote || branch: unreachable through real git — @{upstream} always resolves to remote/branch, never a bare remote name.

Exclusions / notes

  • The .git file half of ae9721e is covered by upstream's own mock test and is not reproducible mock-free on this machine: modern engines no longer backtrack on the old gitdir:\s*(.+)$ pattern (2ms under bun and Node), and real git refuses to consume a whitespace-run .git file.
  • git in the tests is invoked through Bun.spawnSync under bun because sibling suites mock node:child_process and that mock leaks across files in the single-process bun runner; vitest/Node keeps file isolation and uses the real spawnSync.
  • When those leaked mocks are present, runGit itself is broken (every call resolves to null), so both real-git suites probe runGit end to end once (fresh repo, dummy remote, non-empty output) and skip when that lookup is broken; vitest always runs them, as does an isolated bun test <file>. The first CI run caught exactly this: runGit-dependent tests failed on Linux while the null-expectation ones passed. Pure-unit describes (parseFilterConfig, getFilterOverrideArgs, withGitHardeningEnv, parseRemoteUrl) run under every runner unconditionally.
  • Full suite on this branch: bun 3551 pass / 1 fail — the known macOS-only git-hardening-exec LFS case (pre-existing, see verify(tooling): Stryker mutation tooling + upstream 9246095 pilot (batch 1) #55 notes). An occasional ItemsEditor flake in full runs is unrelated (green in isolation).

Commits

  1. 007fd6e — test(git): reproduce upstream 810d424 on macOS
  2. 2b38802 — test(git-remote): reproduce upstream ae9721e on macOS

🤖 Generated with Claude Code

Follow-up (42e9de3)

Rebased onto main: the reproduction commits and the Stryker/update-checker tooling already landed in main via #57, so this PR now carries only the review follow-up — dedupe of the real-git test boilerplate into git-test-helpers.ts, a dead conditional removed, .gitignore anchor fix. See the resolution comment for the addressed/declined reviewer observations.

@axisrow
axisrow force-pushed the verify/git-hardening branch 2 times, most recently from 74e5c22 to 2b38802 Compare October 11, 2026 18:48

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Верdict: approve — тесты апстрим-фиксов + Stryker-тулинг

Одобряю. PR тестовый и тулинговый: три новых набора тестов, воспроизводящих апстримные security-фиксы (810d424 git hardening, ae9721e линейные парсеры, 9246095 semver-guard), плюс конфигурация Stryker. Изменений исполняемого кода нет.

Проверено сверкой ожиданий с исходниками (без запуска):

  • Ожидания parseFilterConfig / getFilterOverrideArgs / withGitHardeningEnv сходятся с src/utils/git-hardening.ts: грани-кейсы (пустой scope, filter..x, user.alice.name, имя драйвера с =, git-crypt с относительным путём или путём внутри repo, mismatch ключа) корректны.
  • Ожидания parseRemoteUrl (ssh/url формы, порт только для http(s), nested owners, набор reject-ов) сходятся с git-remote.ts; линейный split/filter действительно заменяет бэктрекинг-regex, так что тест на 100k слэшей различает до/после фикса.
  • Ожидания checkForUpdates / runGlobalPackageInstall сходятся с guard-ом RELEASE_VERSION в update-checker.ts; PATH-stub npm корректно сохраняется и восстанавливается.
  • fsmonitor-тест построен верно: hook прописывается в local config уже после commit, так что setup-команды его не триггерят, а runGit идёт через execGit с GIT_HARDENING_ARGS.
  • bun.lock — чисто аддитивный (только stryker-зависимости), удалений нет. describe.skipIf — устоявшийся в репо паттерн (см. git-hardening-exec.test.ts).

Наблюдения (не блокирующие, только по желанию):

  1. upstream-git-linear.test.ts:115 — мёртвый тернарник protocol === :https: || protocol === :http: ? :github.com: : :github.com: (обе ветки одинаковые) — можно упростить до константы.
  2. Временной ассерт toBeLessThan(1000) у теста линейности: quadratic-сценарий даёт минуты, линейный — миллисекунды, так что порог можно поднять (например до 5000) без потери различающей силы — меньше риск флака на загруженной машине.
  3. gitSync / BunRuntime / probe-boilerplate (~50 строк) продублированы в обоих upstream-git-* файлах — при случае можно вынести в общий test-helper.
  4. Реальные git-сьюты, вероятно, скиппятся в полном bun test (CI) из-за утечки vi.mock child_process — это осознанный и задокументированный в комментариях компромисс, но стоит помнить: флагманский fsmonitor-тест в CI может не выполняться; реально он гоняется в изолированном прогоне файла и под vitest/stryker-конфигом.
  5. upstream-update-checker.test.ts, в отличие от git-сьютов, не защищён skipIf-пробой от той же утечки mock-ов — но там падение было бы громким, а не тихим, так что допустимо.
  6. Новые real-git сьюты не гейтятся на win32 (в отличие от git-hardening-exec.test.ts): /dev/null и #!/bin/sh не работают на Windows; для CI (ubuntu) и macOS разработки некритично.
  7. .gitignore: паттерн reports матчит одноимённые папки на любом уровне; корневой reports/ был бы точнее.

axisrow added a commit that referenced this pull request Oct 12, 2026
Review follow-up on PR #56 (AO reviewer, non-blocking observations):

- move gitSync / COMMIT_ENV / the runGit availability probe into the
  existing git-test-helpers.ts instead of duplicating them in both
  upstream-git-* suites
- drop a dead conditional in the url-form parseRemoteUrl expectations
- anchor the Stryker reports ignore to the repository root

Co-Authored-By: Claude Code <noreply@anthropic.com>
Review follow-up on PR #56 (AO reviewer, non-blocking observations):

- move gitSync / COMMIT_ENV / the runGit availability probe into the
  existing git-test-helpers.ts instead of duplicating them in both
  upstream-git-* suites
- drop a dead conditional in the url-form parseRemoteUrl expectations
- anchor the Stryker reports ignore to the repository root

Co-Authored-By: Claude Code <noreply@anthropic.com>
@axisrow
axisrow force-pushed the verify/git-hardening branch from 43f5d33 to 42e9de3 Compare October 12, 2026 04:47
@axisrow

axisrow commented Oct 12, 2026

Copy link
Copy Markdown
Owner Author

Статус после ребейза на main (1cccb73): воспроизводящие коммиты этой ветки (007fd6e, 2b38802, а также тулинг и update-checker-сьют) уже попали в main в составе мержа #57 — при ревейзе git дропнул их как «patch contents already upstream». В PR остался единственный follow-up 42e9de3 (дедупликация бойлерплейта), применённый поверх уже смёрженных копий тестов. Оператор решает: мержить его как маленький cleanup-PR или закрыть #56 как superseded by #57.

Разбор наблюдений ревьюера (все были помечены как необязательные):

Исправлено

  1. Мёртвый тернарник в parseRemoteUrl url-form ожиданиях → убран, константа 'github.com'.
  2. Дублирование gitSync / COMMIT_ENV / probe (~50 строк в каждом из двух сьютов) → вынесено в существующий src/utils/__tests__/git-test-helpers.ts (добавлен probeRealRunGitAvailable(), вызывается в каждом сьюте на уровне модуля — поведение то же). Дифф стал короче на 35 строк.
  3. .gitignore: reports → /reports/ (якорь к корню).

Отклонено

  • Поднять порог toBeLessThan(1000) до 5000: измеренный pre-fix диапазон 5931–7073 мс, так что порог 5000 сжимает различающую силу красного теста с ~6× до ~1.2×. Текущий порог даёт ~500× запаса над линейным путём (~2 мс) — флак-риск ниже, чем потеря чувствительности к регрессии.
  • skipIf-гейт новых сьютов на win32: в репо нет Windows-CI, а git-hardening-exec гейтится исторически; добавлять гейты без реального сигнала — спекуляция. Вернёмся, если появится Windows-раннер.
  • Прочее (скип реальных git-сьютов в полном bun test из-за утечки vi.mock, отсутствие гейта в update-checker-сьюте) — осознанные, задокументированные в комментариях компромиссы, без изменений.

Проверка: bun run lint чисто; 3 сьюта 70/70 под bun test (изолированно) и под vitest; в мультифайловом bun-прогоне с мокающими сьютами fsmonitor-тест скипается — прежнее задокументированное поведение, не изменение.

🤖 Generated with Claude Code

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve — обновление PR: sanitize-тесты + дедуп boilerplate

Одобряю. С прошлого ревью (head 2b38802) в ветке два коммита: 1cccb73 — влитый squash из #57 с новым upstream-terminal-sanitize.test.ts (апстрим 3d94211, GHSA-8j6h-phgx-ww84), и 42e9de3 — вынос gitSync / COMMIT_ENV / probe в существующий git-test-helpers.ts. Исполняемого кода продуктовой логики PR не меняет — только тесты и тест-инфраструктура.

Проверено сверкой с исходниками (без запуска):

  • Все 18 кейсов sanitize-теста трассированы по terminal-sanitize.ts: OSC/DCS/SOS/PM/APC до BEL / C1_ST / ESC-backslash, CSI с параметрами, intermediates и final byte (включая boundary: пробел-посредник, ? как приватный маркер, @ и ~ как final), незакрытые CSI/DCS до конца строки, C1-controls, charset-escape ESC ( B — ожидания совпадают с реализацией, включая хитрые случаи a\x1b[2\x07Jb → aJb и a\x1b/\x07bc → abc.
  • renderOsc8Link: URL с control-символом возвращает plain label, безопасный — оборачивается в OSC 8 ✓ (isSafeHyperlinkUrl / SAFE_URL).
  • Тест идёт через preRenderAllWidgets, то есть через реальную fix-строку renderer.ts:943, где sanitize применяется к выводу виджета; custom-text рендерит customText как есть, DEFAULT_SETTINGS лейблов не добавляет — путь чистый.
  • Дедуп: чистое добавление трёх экспортов в существующий src/utils/tests/git-test-helpers.ts; isolateGitWorkingDirectory / expectGitExecOptions / gitCommandOf не тронуты, обе upstream-git-сьюты переключены на хелпер, локальные копии удалены.
  • Новые импорты хелпера (runGit / clearGitCache из ../git) сдвигают момент первой загрузки git.ts в общем bun-процессе, но skip решает probeRealRunGitAvailable в рантайме — поведение не хуже прежнего. Хелпер не *.test.ts, в stryker-профиль не попадает.

Из наблюдений прошлого ревью учтено: мёртвый тернарник в upstream-git-linear.test.ts упрощён до константы, .gitignore-паттерн reports стал корневым /reports/, boilerplate вынесен в общий хелпер. Осталось как есть (и осталось необязательным): порог timing-ассерта 1000ms; upstream-git-сьюты без win32-гейта — для ubuntu CI и macOS-разработки некритично.

Примечание по процессу, не блокирующее: 1cccb73 несёт в сообщении метку (#57) — если PR #57 остаётся открытым с тем же содержимым, один из двух дублей стоит закрыть после мержа.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant