fix(env): only stick to a candidate the active profile actually reaches (#682) - #715
Conversation
…es (review) 02c93fe's resolveActiveShellProfile scanned every SHELL_PROFILE_CANDIDATE_NAMES entry for a matching block and returned the first hit, in a fixed order (.zshrc, .bashrc, .bash_profile, .bash_login, .profile). That's broader than the Git-for-Windows-forwarding case it was written for: a stale block a pre-Tencent#682 install left in .bashrc would outrank a correctly order-picked .profile that hasn't been written to yet, since .bashrc sorts earlier in the candidate list — silently reintroducing Tencent#682 for exactly the installs upgrading through this fix, with doctor unable to catch it because the stale block is well-formed where it sits. Reworked to start from detectShellProfile's order-based pick (the file the current environment actually reads) and only diverge from it when that pick's own content references another candidate by a home-relative path (~/.bashrc, $HOME/.bashrc) — the shape Git for Windows' generated forwarding file actually takes. A block sitting in a candidate the pick never reaches is no longer preferred over the pick, regardless of what it contains. Also caught and fixed a case of exactly the failure mode this PR is about: the first cut of the forwarding check was a bare substring match on the candidate's filename, and my own test's plain-English comment ("...unrelated to .bashrc") satisfied it. Tightened to require the home-relative reference form a real sourcing line uses. Verified both scenarios end-to-end on a real Windows host: - The exact bot-reported upgrade case (stale .bashrc block, empty .profile, no forwarding between them): pull now writes into .profile and correctly flags .bashrc as stale; doctor reports delivery healthy. - The Git-for-Windows forwarding case from the prior round: still sticks to .bashrc through the generated .bash_profile, no duplicate, no stale-block warning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Findings
The PR description includes both a test plan and a detailed real-CLI end-to-end record, so no testing-description finding is needed. |
…ly (review) Two P1s from the bot's review of Tencent#715: - resolveActiveShellProfile's reachability check was a bare substring search on the active pick's content. A comment mentioning a filename (never executed) or a longer file sharing the same prefix (~/.bashrc.local) would both satisfy it, letting a stale block win the same way Tencent#682 did. Replaced with referencesCandidate(): strips full-line comments, splits each remaining line into statements on &&/||/;, and only counts a statement whose first word is literally `.` or `source` and whose second word is an anchored home-relative reference to exactly that candidate. - The check only followed one hop: .bash_profile sourcing .profile sourcing .bashrc (the common Debian .profile pattern, sourcing .bashrc for interactive shells) would miss a block two hops away and inject a duplicate. Reworked into a loop that walks the chain of files the pick actually sources, with a visited set for cycle protection, stopping at the first one that carries the block. Also fixed the P2: EnvHandler.detectShellProfile's doc comment still claimed it "stays on whichever candidate already carries this scope's block" unconditionally, which stopped being true once reachability was required. Verified end-to-end on a real Windows host: - The new two-hop chain (.bash_profile -> .profile -> .bashrc, block in .bashrc): resolves to .bashrc, no duplicate, doctor fully clean. - Re-ran the Git-for-Windows one-hop scenario and the Tencent#682 upgrade scenario from the prior round — both still correct, no regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both P1s addressed in `75f3eac`, verified end-to-end. Substring match (`shell-profile.ts:249`): agreed. Replaced with `referencesCandidate()`: drops full-line comments, splits each remaining line into statements on `&&`/`||`/`;`, and only counts a statement whose first word is literally `.` or `source` and whose second word is an anchored home-relative reference to exactly that candidate — so `# source One-hop-only reachability (`shell-profile.ts:243`): also agreed, and worth calling out that the chain you named — `.bash_profile` sourcing `.profile` sourcing `.bashrc` — isn't hypothetical, it's the standard Debian/Ubuntu `.profile` template (sources `.bashrc` for interactive shells). Reworked into a loop that walks the chain of files the order-based pick actually sources, with a visited set for cycle protection, stopping at the first one carrying the block. Doc comment (`env.ts:417`, P2): fixed — no longer claims unconditional stickiness. Real-machine verification, three scenarios:
Added 4 new tests: comment-only mention, filename-prefix collision, the two-hop chain, and a reference-cycle case (asserting it falls back to the order-based pick rather than hanging). `npx tsc --noEmit` clean; full `vitest run` at the same pre-existing 30-file/66-test failure baseline as unmodified `main` (confirmed via `git stash` comparison on this exact run) — no new failures, 4 new passing tests. |
|
Findings
Earlier Findings
|
…y (review) Two more P1s from the bot's round-10 review of 75f3eac: - The traversal committed to the first referenced candidate in SHELL_PROFILE_CANDIDATE_NAMES's fixed priority order and gave up if that branch was a dead end, instead of trying every candidate the current file actually references. Git for Windows' own generated .bash_profile sources both .bashrc and .profile in one file — if the real block sits in .profile but .bashrc (sorting earlier) has none, the walk stopped at .bashrc without ever trying .profile. Reworked into a breadth-first search over the whole reference graph. - Splitting statements on `||` treated its right side as unconditionally reached, but `||`'s right side only runs if the left side fails, which isn't something this code can establish. `source ~/.profile || source ~/.bashrc` would mark .bashrc reachable even when .profile succeeds. Statements no longer split on `||`; a `source`/`.` sitting only after it is folded into its left side's statement and never recognized as its own reference, so it's never preferred over a block the left side already reaches. Conservative by construction: worst case is falling back to the order-based pick (the pre-Tencent#693-fix behavior), never a false "reachable". Verified the exact branching scenario end-to-end on a real Windows host: .bash_profile with the literal Git-for-Windows-generated content (sources both .bashrc and .profile), .bashrc empty, real block in .profile — resolves to .profile, no duplicate, doctor fully clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both real, addressed in `aaa1142`. Priority-order traversal (`shell-profile.ts:277`): agreed — `.find()` committed to whichever referenced candidate sorted first in `SHELL_PROFILE_CANDIDATE_NAMES`, not the one that actually led anywhere. Reworked into a breadth-first search over the whole reference graph (queue + visited set), so every candidate a file references gets tried, not just the first. `||` treated as unconditional (`shell-profile.ts:225`): also agreed. Statements no longer split on `||` — a `source`/`.` sitting only on its right side is folded into the same statement as its left side and never recognized as its own reference, so it can never be preferred over a block the left side already reaches. This is deliberately conservative: worst case for something more exotic than `||` (nested conditionals, subshells) is falling back to the order-based pick and injecting a duplicate — the pre-#693 behavior, not a false "reachable." Real-machine verification of the exact branching case: `.bash_profile` with the literal Git-for-Windows-generated content (`test -f ~/.bashrc && . ~/.bashrc` / `test -f ~/.profile && . ~/.profile`, both in one file), `.bashrc` empty, the real block in `.profile`: ``` Resolves to `.profile` even though `.bashrc` sorts earlier in the candidate list. Added 2 more tests for these (branching reference, `||`-conditional). `npx tsc --noEmit` clean; full `vitest run` at the same pre-existing 30-file/66-test baseline as unmodified `main` — no new failures, 6 new passing tests total across this round and the last. |
|
Findings
Resolved
|
…s (review) Two more P1s from the bot's round-11 review of aaa1142, both about referencesCandidate() trusting shell control flow it can't actually evaluate: - Any `&&` was treated as making its right side reachable, without checking what the left side's condition even was. A guard like `[ "$TERM_PROGRAM" = vscode ] && source ~/.bashrc` would mark .bashrc reachable unconditionally, even though it only runs inside VS Code. Also flagged: a source sitting inside a multiline `if` body looks, line by line, identical to a top-level one. - Folding `||`'s right side into its left statement (the round-9 fix) went too conservative the other way: `source ~/.profile || source ~/.bashrc` DOES guarantee .bashrc runs when ~/.profile doesn't exist, and the resolver was never even trying it. Rather than growing another ad hoc regex tweak, rewrote referencesCandidate() around what it can actually verify without a real shell parser: - Unconditional: a bare `. REF` / `source REF` — but nothing inside an `if` block counts, conditional or not. An `if`'s condition is opaque to a line scanner; trusting some conditions and not others would just be guessing. - Existence-gated `&&`: only the self-referential idiom `test -f REF && . REF` / `[ -f REF ] && . REF`, where the tested path and the sourced path are the same candidate — the one `&&` condition this code can independently verify, by visiting that candidate itself later in the search. - `||` fallback: the left side always counts (always attempted); the right side counts only when the left side's own target file does not exist on disk — the one case an `||` fallback is actually guaranteed to run. Anything this can't resolve either way is never trusted: the search just doesn't queue that candidate, and the caller falls back to the order-based pick — at worst a harmless duplicate block (the pre-Tencent#693-fix behavior), never a false "reachable" that would reintroduce Tencent#682. Verified end-to-end on a real Windows host: re-ran the core Git-for-Windows two-pull scenario from Tencent#693 (self-referential &&, still recognized) with no regression. Added 3 unit tests for the new boundaries: || recognized when the left target is missing, a non-existence && condition rejected, and a source nested inside an if block rejected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…boundary (review) Round-11 P2: both docs described only a single directly-referenced candidate, but the resolver has followed transitive chains since 75f3eac and now only trusts specific verifiable && / || forms (60a2da0). Describes the Debian .profile -> .bashrc two-hop case alongside the Git-for-Windows one, and names the three reference shapes recognized (bare source, self-referential existence-gated &&, existence-checked || fallback) and that if-bodies are never trusted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both P1s real, addressed in `60a2da0` (docs P2 in `158df65`). Unverifiable `&&` (`shell-profile.ts:232`): agreed — any `&&` granted reachability regardless of what the left side actually tested, and a source nested in a multiline `if` body looks, line by line, identical to a top-level one. Rather than another targeted regex patch, rewrote `referencesCandidate()` around exactly what it can verify without a real shell: a bare unconditional source (never inside an `if` block — the condition is opaque either way, trusting some and not others would just be guessing), and `&&` only for the self-referential existence idiom (`test -f X && . X`, same `X` both sides) — the one condition independently checkable by visiting that candidate later in the search. `||` too conservative (`shell-profile.ts:225`): also agreed, and the fix composes with the above: the left side of `||` always counts (always attempted); the right side counts only when the left side's own target file doesn't exist on disk — the one case that fallback is guaranteed to run, checked via the same file I/O the search already does. I want to flag directly where I'm drawing the line here, since round 9 → 10 → 11 has each found a real gap in the previous round's shell-construct handling: this now recognizes three verifiable shapes (bare source outside any `if`, self-referential existence-gated `&&`, existence-checked `||` fallback) and nothing else — no nested conditionals, no `case`, no variable expansion in paths, no subshells. That's a deliberate boundary, not an oversight: general shell evaluation is undecidable without actually running a shell, and something more exotic than these three shapes falls back safely to the order-based pick (a harmless duplicate block, the exact pre-#693 behavior) rather than a false "reachable" — the failure direction this whole resolver exists to avoid. If a specific additional pattern shows up on real hardware the way the Debian `.profile` chain and the Git-for-Windows generator did, I'll take another look at it on that basis rather than in the abstract. Re-ran the core Git-for-Windows two-pull scenario from #693 on the real Windows host — unaffected: Added 3 tests for the new boundaries (`||` recognized when the left target is missing, a non-existence `&&` condition rejected, a source nested in an `if` rejected) — 35/35 in `shell-profile.test.ts`. `npx tsc --noEmit` clean; full `vitest run` at the same pre-existing 30-file/66-test baseline, +3 new passing tests (one unrelated Vitest worker-IPC timeout on this run, not a real test failure, confirmed by identical counts). Docs (both languages) updated to describe the transitive chain and the three recognized reference shapes. |
|
Findings
Resolved
|
…ze block-skip (review) Round 12 found three more real gaps in referencesCandidate(), plus one I agree isn't worth chasing further (see PR reply): - The quote check accepted `source "~/.bashrc"` and `source '$HOME/.bashrc'` as valid references, but a shell never tilde-expands inside any quotes and never variable-expands inside single quotes — both source a literal, near-certainly nonexistent path. Tightened to the three forms that actually expand: bare `~/name`, and `$HOME/name` either bare or double-quoted. - `&&`'s left side is always attempted, the same as `||`'s — `source ~/.bashrc && echo ready` does reach .bashrc regardless of the trailing command, but the old "whole statement must be exactly `. REF`" check missed it. The leftmost command before the first `&&` (or no `&&` at all) is now checked the same way `||`'s left side already was. - Only `if`/`fi` was tracked, so a source inside an uncalled function, a non-selected `case` arm, or a loop body — none of them any more guaranteed to run than an `if` body — was wrongly treated as top-level. Generalized the "don't trust it" depth counter to cover for/while/until, case/esac, and function/brace groups too, sharing one counter since we only need to know whether we're inside *any* of them, not which one. Declined to extend if-body trust to cover the standard nested Debian `.profile` template (`if [ -n "$BASH_VERSION" ]; then if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc"; fi; fi`) — doing so would mean trusting the outer `$BASH_VERSION` check, which is exactly the class of unverifiable shell condition this design has refused since round 11. Fixed the docs instead: they previously (incorrectly) claimed this exact template was recognized; now they say plainly that nested conditionals of any kind fall back to the order-based pick. Verified end-to-end on a real Windows host: re-ran the Git-for-Windows two-pull scenario unaffected. Added 6 unit tests for the new boundaries (invalid vs. valid quoting, &&'s left side, function/case/ loop bodies). 41/41 in shell-profile.test.ts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Three of four are real, addressed in `0e05baa`. Pushing back on the fourth, with a docs fix instead. Invalid quoting (`shell-profile.ts:208`): agreed, and this one's a good catch — a shell never tilde-expands inside any quotes and never variable-expands inside single quotes, so `source " `&&`'s left side (`shell-profile.ts:267`): agreed — `&&`'s left side is always attempted, same as `||`'s already was. `source ~/.profile && echo ready` does reach `.profile` regardless of the trailing command. The leftmost command before the first `&&` (or no `&&` at all) is now checked the same way `||`'s left side already is. Only `if` tracked (`shell-profile.ts:256`): agreed — a source inside an uncalled function, a non-selected `case` arm, or a loop body is no more guaranteed to run than an `if` body is, and treating it as top-level was inconsistent with my own stated rule. Generalized the depth counter to cover `for`/`while`/`until`, `case`/`esac`, and function/brace groups too — one shared counter, since all that matters is whether we're inside any of them. Debian `.profile` template (`shell-profile.ts:260`): this is where I'll push back, though the report itself is accurate — I did overclaim it in the docs, and I've fixed that. The actual standard template is: Real-machine re-verification of the Git-for-Windows scenario from #693 — unaffected: Added 6 tests for the new boundaries (invalid vs. valid quoting, `&&`'s left side, function/case/loop bodies) — 41/41 in `shell-profile.test.ts`. `npx tsc --noEmit` clean; full `vitest run` at the same pre-existing 30-file/66-test baseline, +6 new passing tests. Docs (both languages) corrected. |
|
Findings
Resolved
|
…&& in profile scanning (review)
Round 13 review found five genuine structural gaps in referencesCandidate,
all fixed by moving open/close-block detection to per-statement (post
`;`-split) instead of per-line, and adding a logicalLines() preprocessing
pass:
- Backslash-continued lines were scanned independently, losing the
conditional context of the line they continue (`cond && \` followed by
`source X` on the next line looked unconditional).
- A one-line `if ...; then ...; fi` only incremented depth (matched via
the whole-line "opens" check) and never saw its own `fi` close it,
permanently disabling recognition of every later unconditional source
in the file. Two-line function definitions (`fn()` then `{` on its own
line) double-incremented for the same reason.
- The existence-gated `&&` guard was fully anchored, so a guarded source
followed by further `&&`-chained commands (`[ -f X ] && . X && export Y`)
didn't match even though the guard still holds.
- Comment stripping only skipped whole-comment lines; a comment following
a semicolon on the same line was still split into a "real" statement.
- Heredoc bodies were scanned as literal executable lines.
Declined the sixth (recognizing the Debian/Ubuntu nested
`if [ -n "$BASH_VERSION" ]; then if [ -f ... ]; then . ...; fi; fi`
template) for the same reason given in review round 12: the outer
condition is unverifiable without a real shell, and this resolver's
explicit, repeatedly-restated design boundary is to never trust an
unverifiable condition — falling back to the order-based pick (a
harmless duplicate block) is the intended safe behavior there, not a bug.
Verified with 6 new unit tests (47/47 passing) plus a standalone real-fs
script driving the actual resolveActiveShellProfile against a scratch
HOME for all seven round-13 scenarios (all pass). Full suite unchanged
at the pre-existing 30-failed-file/66-failed-test Windows-host baseline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Five of six real, addressed in Backslash continuation ( Depth counter broken by one-line Existence guard too strict for trailing Comment-then-semicolon ( Heredoc bodies ( All five verified with 6 new unit tests (47/47 passing) and a standalone script driving the real, built Debian Full regression suite unchanged from the pre-existing Windows-host baseline (30 failed files / 66 failed tests, confirmed via |
|
Findings
Resolved
|
… return/exit, N-way || (review) Round 14 review found six more genuine gaps in referencesCandidate, all fixed: - The `;`/`&&`/`||` splits were plain `String.split`, so a separator character inside a quoted argument (e.g. `printf '%s' 'x; source ~/.bashrc; y'`) was treated as a real statement boundary, inventing an executed source out of string data. Added splitTopLevel(), a small quote-aware splitter (tracks single/double-quote spans, skips separators inside them) used everywhere a naive split was previously used. - `(...)` subshells weren't tracked as an unverified-block construct — a source inside one always runs, but its exports never reach the caller, so it must not count as reaching a candidate any more than an `if` body does. Added to opensUnverifiedBlock/closesUnverifiedBlock alongside the existing if/for/while/until/case/function handling. - An unconditional, top-level `return`/`exit` ends the file's control flow right there; anything textually after it was still being scanned as if reachable. Added a `halted` flag set on a bare return/exit statement, gating everything after it for the rest of the scan. - `sourceOf` required the source's argument to be the entire statement, so `. "$HOME/.bashrc" 2>/dev/null` and `source ~/.bashrc extra_arg` (both valid, both really sourcing the target) went unrecognized. Relaxed to capture just the first argument and allow anything after it. - The `||` fallback only handled exactly two operands — a three-way chain like `source ~/.profile || source ~/.bash_login || source ~/.bashrc` wasn't recognized at all, not even the always-attempted left side. Generalized to N operands: each one counts only when every operand before it is a recognized source whose target is verifiably missing from disk. - Multiple heredocs opened by one command (`cat <<A <<B`) only tracked one terminator, so the second heredoc's body was scanned as real statements once the first terminator was seen. heredocEnd is now a queue of terminators consumed in order. Declined the seventh finding again (the Debian/Ubuntu nested `if [ -n "$BASH_VERSION" ]` template) for the same reason given in rounds 12 and 13: the outer condition is unverifiable without a real shell, and this resolver's explicit design boundary is to never trust one — the order-based-pick fallback (a harmless duplicate block) is the intended safe outcome there, not a bug. Verified with 8 new unit tests (55/55 passing) plus a standalone real-fs script driving the actual built resolveActiveShellProfile for all nine round-14 scenarios (all pass, including confirming the Debian pushback case is unchanged). Full suite unchanged at the pre-existing 30-failed-file/66-failed-test Windows-host baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Six of eight real, addressed in Quote-unaware Subshells not tracked ( Control-flow terminators ignored ( Only single-argument source recognized (
Multi-heredoc terminator tracking ( All six verified with 8 new unit tests (55/55 passing) and the standalone real-fs script against the actual built resolver, extended to all nine round-14 scenarios (all pass). Debian
Full regression suite unchanged from the pre-existing Windows-host baseline (30 failed files / 66 failed tests) — no new failures. |
|
Findings
Resolved
|
…, closed recognizer (review) Round 15 review found nine more genuine parsing bugs, most of them direct consequences of the general-purpose statement/operand machinery added in rounds 13-14 (quote/escape-aware `;`/`&&`/`||` splitting, N-way `||` chains, trailing-argument tolerance on `source`). It also included a meta-finding, correctly: this had grown into a large, incomplete ad-hoc shell parser for what should be a narrow forwarding-detection case, and each round's fix was mostly patching bugs the previous round's own machinery introduced. Full shell parsing is undecidable without a real shell; chasing it one adversarial regex at a time was never going to finish, and it was already producing real regressions (the round-14 `sourceOf` relaxation meant to recognize valid trailing arguments also started recognizing `source ~/.bashrc | cat` and `source ~/.bashrc &`, both of which run in a subshell and never actually reach the caller). Replaced `referencesCandidate`'s open-ended grammar with a closed recognizer of exactly two forms, each matched as a complete logical line: - bare unconditional `. REF` / `source REF` - the self-referential existence guard `test -f REF && . REF` / `[ -f REF ] && . REF` — the literal line Git for Windows itself generates Deleted entirely: quote/escape-aware statement splitting (no longer needed — nothing is split into statements anymore), `||` fallback handling (both the original two-operand and round 14's N-way generalization), trailing-argument/redirection tolerance on `source` (the source of the pipe/background regression above), and comment-stripping (unnecessary now — a line with anything extra on it simply fails the exact-match check, which is a large part of why the statement machinery could be deleted rather than just patched again). Kept, since dropping them would reopen a real false-positive risk rather than just narrow scope: block-depth tracking for `if`/`for`/`while`/`until`/`case`/`select`/function/subshell/brace-group (content inside is either conditional or non-propagating, generalized this round with `select` and a fixed one-liner if/for/while/until/case collapse so a self-contained one-liner doesn't corrupt depth tracking for the rest of the file), heredoc body skipping (fixed three real bugs in it: a `<<<` here-string was mistaken for a `<<` heredoc and swallowed the rest of the file; a non-`-` heredoc's terminator was compared with `.trim()`, letting an indented look-alike end it early; the delimiter charset was `\w` only, missing real delimiters like `END-CONFIG`), a `return`/`exit` halt flag (cheap, and the alternative — textually dead code after an unconditional exit still being scanned — is a genuine false positive, however unlikely the pattern), and joining a line ending in `\`, `&&`, or `||` onto the next (real, unremarkable shell continuation with no backslash required for the latter two — the risk this closes isn't hypothetical: an unrelated trailing `&&` followed by an unconditional-looking `source` on the next line is exactly the shape that would have produced a false "reachable"). Declined the Debian/Ubuntu nested-`if` finding a fourth time, unchanged from rounds 12-14: the outer `$BASH_VERSION` check is unverifiable without a real shell, and this resolver's explicit boundary is that an unverifiable condition is never trusted. The `||`-existence-only pushback from round 14 is now moot — `||` isn't recognized in any form. Net change to shell-profile.ts is negative (-244/+something smaller) despite fixing more bugs than it added, confirming this is a real simplification rather than another round of patches. Verified with an updated unit test suite (61/61 passing — six tests for now-out-of-scope behavior replaced with tests confirming the safe fallback, new tests added for every round-15 fix that was kept) and a standalone real-fs script against the actual built resolver covering all twelve round-15 scenarios (all pass, including the real motivating Git-for-Windows case and the still-declined Debian template). Full suite unchanged at the pre-existing 30-failed-file/66-failed-test Windows-host baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Taking the last finding seriously and changing course rather than patching further: "This change adds a large, incomplete shell parser to address a narrow forwarding case... violating the repository's requirement to reject speculative over-engineering." That's correct. Looking back at rounds 13-14, most of what got added was patching bugs that the previous round's own general-purpose machinery introduced (quote/escape-aware splitting, N-way
Deleted (this is what "large, incomplete shell parser" was actually referring to): quote/escape-aware statement splitting (nothing gets split into statements anymore, so it's not needed), Kept — not narrowed away, because dropping them reopens a real false-positive risk rather than just trimming scope:
Net change to Debian
Verified with an updated unit suite (61/61 passing — six tests for now-out-of-scope behavior replaced with tests confirming the safe fallback, new tests for every fix that was kept) and a standalone real-fs script against the actual built resolver covering all twelve round-15 scenarios, including the real Git-for-Windows case and the still-declined Debian template (all pass). Full regression suite unchanged from the pre-existing Windows-host baseline. |
|
Findings
Resolved
|
Summary
Follow-up to #693. That PR's squash merge (
cc38871) landed pinned to commit02c93feon the branch, one commit short of1ed0141— the fix for a P1 the review bot found in02c93feitself.maincurrently carries the buggy version. Reported directly against #682 by @CarlosWonMore, from the same real-machine fixture used throughout that review.The bug on current
main:resolveActiveShellProfile()(added in #693 to stop Git for Windows' auto-generated.bash_profilefrom causing duplicate env blocks) scans every candidate file in a fixed order (.zshrc,.bashrc,.bash_profile,.bash_login,.profile) and returns the first one carrying a matching block. Since.bashrcsorts before.profile, a stale block a pre-#682 install left in.bashrcoutranks a correctly order-picked, not-yet-written.profile— silently reintroducing #682 for exactly the installs upgrading through this fix. Worse than before #693:doctorno longer catches it, since the stale block is well-formed where it sits.Fix: start from
detectShellProfile's order-based pick (the file the current environment actually reads), and only diverge from it when that pick's own content references another candidate by a home-relative path (~/.bashrc,$HOME/.bashrc) — the shape a real Git-for-Windows-generated forwarding line takes. A block sitting in a candidate the pick never reaches is no longer preferred over the pick, no matter what it contains.This is commit
1ed0141from #693's branch, cherry-picked onto currentmain(clean, no conflicts — the affected files were untouched by anything merged after #693).Fixes #682
Test Plan
npx tsc --noEmitpassesnpx vitest run— same pre-existing 30-file/70-test Windows-host-only baseline as unmodifiedmain(unrelated POSIX-path-literal test fixtures); no new failures; all tests for this change passresolveActiveShellProfileregression test for the exact upgrade case (stale.bashrcblock outranking a correctly-read, not-yet-written.profile), plus a corrected test that had asserted the old, buggy broad-stickiness behaviorReal, end-to-end verification — built
dist/index.jsfrom this branch, reproduced @CarlosWonMore's exact reported state (scratch$HOME, real local git team repo,.bashrccarrying a well-formed but unread pre-#682 block,.profilepresent with nothing in it yet):New block correctly lands in
.profile(the file Git Bash's login shell actually reads); the stale.bashrccopy is surfaced as its own non-blocking cleanup item, not folded into delivery health.Also re-verified the Git-for-Windows forwarding scenario from #693 still holds (no regression): a fresh install writing to
.bashrc, followed by Git for Windows auto-generating~/.bash_profilesourcing it — still sticks to.bashrc, no duplicate block.