Read the push shim's refspecs off the positions the matcher found - #85
Conversation
`git -c user.name=x push` was matched and then collected by argv index, so
the branch it publishes was checked nowhere.
VALUE_OPTIONS taught `reading` that `-c` takes the word after it, which is
what makes that invocation match a `push:*` table at all. `collect_git_refs`
was never given the same grammar: it skipped argv[0] as the subcommand and
the next non-option word as the remote, so `git -c user.name=x push` read
`user.name=x` as the remote and `push` as a refspec. The name actually going
onto the forge was collected nowhere, and the fallback that reads it off HEAD
did not run either, because a name had been collected.
Measured against 1.8.0 as installed, in a tree whose policy refuses the
branch name it is standing on:
uphold shim git push refused
uphold shim git -c user.name=x push published
uphold shim git -C elsewhere push published
uphold shim git --git-dir X push published
One grammar, read once. `words` and the new `positional` are one walk in
`scan`, which stops after two positional words for a matcher and after all of
them for a collector; `collect_git_refs` reads the third onward.
An option this grammar cannot classify still shifts the positions by one. The
words after it are read regardless, and a shift that leaves no refspec at all
falls back to HEAD, so what remains is an extra subject rather than a missing
one.
Both tests fail on the old collector: a unit test over four spellings of a
global option, and a CLI test that drives real invocations to a stub git and
asserts the command never ran.
Claude-Session: https://claude.ai/code/session_01K6XKWdtY1VqQZE3E1GH15T
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe shim now shares grammar-aware argv scanning between command matching and Git ref collection. Git pushes with leading global options identify the correct remote and refspecs. Regression tests cover branch rejection, ChangesGit ref collection
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This change keeps Git options from shifting the remote and refspec positions used by the push shim, with targeted tests and project checks reported passing; no actionable merge-blocking risk remains beyond normal review. Sequence Diagram(s)sequenceDiagram
participant GitCLI
participant Shim
participant ArgvScanner
participant GitRefCollector
participant Git
GitCLI->>Shim: invoke git push with global options
Shim->>ArgvScanner: parse argv with Git option grammar
ArgvScanner-->>GitRefCollector: return positional arguments
GitRefCollector-->>Shim: return push refs
alt policy rejects a branch
Shim-->>GitCLI: reject push
else policy allows the branch
Shim->>Git: execute git push
Git-->>GitCLI: return Git result
end
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #85 +/- ##
==========================================
+ Coverage 90.51% 90.59% +0.07%
==========================================
Files 35 35
Lines 11339 11370 +31
==========================================
+ Hits 10264 10301 +37
+ Misses 1075 1069 -6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Closes #84.
VALUE_OPTIONStaughtreadinggit's global grammar, and that is what makesgit -c user.name=x pushmatch apush:*table at all.collect_git_refswas never given the same grammar: it skipped
argv[0]as the subcommand andthe next non-option word as the remote, so that invocation read
user.name=xas the remote and
pushas a refspec. The branch going onto the forge wascollected nowhere, and the fallback that reads it off
HEADdid not runeither, because a name had been collected.
The shim matched, a checker ran, it approved a word nobody was publishing,
and the command exec'd. Exit 0.
What changed
wordswalks argv and stops at two positional wordsscanwalks argv and stops afterstop_afterof themcollect_git_refsreadsargv.iter().skip(1)and the first non-option word after itcollect_git_refsreadsself.positional(argv).iter().skip(2)One grammar in one place: the matcher takes the first two positional words
out of
scan, a positional collector takes all of them. Nothing else in thewalk moved -- the
--terminator, the inline--flag=valuesplit and theunclearaccounting are the same code they were.Measured
A tree whose policy refuses the branch name it is standing on, 1.8.0 as
installed vs this branch:
uphold shim git pushuphold shim git -c user.name=x pushuphold shim git -C elsewhere pushuphold shim git --git-dir X pushuphold shim git -c user.name=x push origin ordinaryWhat is still approximate
An option this grammar cannot classify shifts the positions by one. The words
after it are read regardless, and a shift that leaves no refspec at all falls
back to
HEAD-- sogit push -o ci.skip origin topiccollectsoriginaswell as
topic. An extra subject rather than a missing one, which is thedirection this seam errs in everywhere else.
Tests
Both fail on the old collector and pass here, which was checked by putting
the old body back:
shim::tests::a_global_option_does_not_shift_which_word_the_branch_is--four spellings of a global option, one expected subject each.
shim_cli::a_global_option_does_not_shift_which_word_the_branch_is-- realinvocations against a stub
giton PATH, including the form where the nameappears nowhere in argv and has to come off
HEAD. Asserts the commandnever ran, and that a clean name past the same option still reaches it.
Verification
prek run --all-filesandprek run --all-files --hook-stage manual--every hook passed, coverage floor included
cargo test566 pass,cargo clippy --all-targets,cargo fmt --checkOne thing found on the way, and not fixed here: the git hooks installed in
this checkout are lefthook's, and lefthook is not on PATH, so every
git commitandgit pushprintsCan't find lefthook in PATHand proceeds.The gates above were run by hand for that reason.
Summary by CodeRabbit
Bug Fixes
Tests
HEADhandling, global Git options, rejected pushes, and successful push forwarding.