fix(git): bypass GitPython for stash ops to fix worktree failure (#189)#190
fix(git): bypass GitPython for stash ops to fix worktree failure (#189)#190MementoRC wants to merge 7 commits into
Conversation
…ning (#186) Adds 7 read-only tools so 'mergeable: true, merge_state: blocked' PRs whose blocker lives in a repository ruleset or a code-scanning requirement can be root-caused from MCP git alone (classic branch protection doesn't expose these). Rulesets: github_list_rulesets, github_get_ruleset, github_get_branch_rules. Code scanning: github_list_code_scanning_alerts, github_list_code_scanning_analyses, github_get_code_scanning_default_setup. Secret scanning: github_list_secret_scanning_alerts. Closes #186 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ation - Add per_page/page to the four GHAS list tools (rulesets, code-scanning alerts/analyses, secret-scanning alerts) — endpoints paginate (30 default, 100 max); previously only the first page was returned. - Add __all__ to scanning.py so wildcard re-export hides logger/_build_params. - Drop empty-string query params in _build_params (keep False/0). - Literal-constrain code-scanning state/severity and secret-scanning state for schema-documented validation. - Add 403 / exception error-path tests; use call_args.kwargs in assertions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds 403 tests for github_get_ruleset, github_get_branch_rules, github_list_code_scanning_analyses, github_get_code_scanning_default_setup, and an exception-path test for default_setup, so every GHAS tool has uniform error-path coverage (PR #188 follow-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GitPython's repo.git.stash() exits with code 1 in linked worktrees even when changes exist, because GitPython may propagate GIT_DIR or run in a context where git cannot see the working-tree diff. Switch all four stash operations (push/pop/drop/list) to subprocess.run() with an explicit cwd=repo.working_dir and a cleaned environment that strips GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, and GIT_COMMON_DIR. This matches exactly what a plain shell `git stash` does in the same directory. Also improves error handling: - exit code 1 + "No local changes to save" now returns an info message instead of an error - messages with spaces are passed safely as a single subprocess argument Adds 11 tests covering push (success, message, spaces-in-message, nothing-to-stash, worktree regression, include-untracked), list (empty, with-stash), pop, drop, and drop-by-id. Closes #189 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @MementoRC's task —— View job PR #190 Code Review
SummaryThis PR does two things: (1) fixes a real bug in
|
… repos git 2.54.0 on ubuntu-latest records file permissions at commit time and then sees a mode diff at runtime, causing git stash push to succeed on an otherwise-clean repo. Adding core.fileMode=false before the initial commit in _make_repo() prevents spurious permission-bit diffs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @MementoRC's task —— View job PR #190 Code Review
SummaryThis PR bundles two separate things: (1) a targeted bug fix for
|
| Priority | Location | Issue |
|---|---|---|
| Medium | _stash_ops.py:76-78 |
Check stderr in "No local changes" detection |
| Low | _stash_ops.py (all 4 fns) |
Guard repo.working_dir is None |
| Low | models.py:1022 |
Add validate_branch_name validator to GitHubGetBranchRules.branch or document the intentional omission |
| Nice-to-have | scanning.py |
Add single-page limitation note to listing docstrings |
| Nice-to-have | scanning.py:86-101 |
Validate stash_id format before use |
…k tests - git_stash_push: check stdout OR stderr for "No local changes to save" (some git versions write this to stderr, not stdout) - All four stash functions: guard against repo.working_dir is None (bare repos would silently run git in the wrong directory) - test_git_stash_push_returns_info_when_nothing_to_stash: replace flaky integration test (CI repo is never reliably clean) with two mock-based unit tests covering the stdout and stderr paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @MementoRC's task —— View job PR #190 Code Review
SummaryThis PR bundles two things: a targeted fix for
|
| Priority | Location | Issue |
|---|---|---|
| Low | _stash_ops.py:96,116 |
Validate stash_id format before passing to git |
| Low | models.py:1022 |
Document why branch has no validator, or add one |
| Nice-to-have | scanning.py (all listing fns) |
Add single-page note to docstrings |
| Nice-to-have | test_github_ghas_forensics.py |
Add ValueError/ConnectionError tests for github_get_ruleset and github_get_branch_rules |
The core fix is correct and well-tested. Ready to merge once the low-priority items are addressed (or consciously deferred).
- _stash_ops.py: validate stash_id format (^stash@{N}$) in git_stash_pop
and git_stash_drop before passing to git; rejects unexpected values like
--force that could confuse git even without shell injection risk
- models.py: document GitHubGetBranchRules.branch has no ref-name validator
intentionally — the GHAS rules API accepts glob patterns, not just branch names
- scanning.py: add single-page pagination note to all four listing function
docstrings so callers know to use per_page/page for large repos
- test_github_ghas_forensics.py: add ValueError and ConnectionError tests for
github_get_ruleset and github_get_branch_rules to match coverage of other
GHAS functions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… (#191) * fix(git): bypass GitPython for stash ops to fix worktree failure (#189) GitPython's repo.git.stash() exits with code 1 in linked worktrees even when changes exist, because GitPython may propagate GIT_DIR or run in a context where git cannot see the working-tree diff. Switch all four stash operations (push/pop/drop/list) to subprocess.run() with an explicit cwd=repo.working_dir and a cleaned environment that strips GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, and GIT_COMMON_DIR. This matches exactly what a plain shell `git stash` does in the same directory. Also improves error handling: - exit code 1 + "No local changes to save" now returns an info message instead of an error - messages with spaces are passed safely as a single subprocess argument Adds 11 tests covering push (success, message, spaces-in-message, nothing-to-stash, worktree regression, include-untracked), list (empty, with-stash), pop, drop, and drop-by-id. Closes #189 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(git): address PR #190 review — stderr check, bare-repo guard, mock tests - git_stash_push: check stdout OR stderr for "No local changes to save" (some git versions write this to stderr, not stdout) - All four stash functions: guard against repo.working_dir is None (bare repos would silently run git in the wrong directory) - test_git_stash_push_returns_info_when_nothing_to_stash: replace flaky integration test (CI repo is never reliably clean) with two mock-based unit tests covering the stdout and stderr paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(git/github): address final PR #190 review items - _stash_ops.py: validate stash_id format (^stash@{N}$) in git_stash_pop and git_stash_drop before passing to git; rejects unexpected values like --force that could confuse git even without shell injection risk - models.py: document GitHubGetBranchRules.branch has no ref-name validator intentionally — the GHAS rules API accepts glob patterns, not just branch names - scanning.py: add single-page pagination note to all four listing function docstrings so callers know to use per_page/page for large repos - test_github_ghas_forensics.py: add ValueError and ConnectionError tests for github_get_ruleset and github_get_branch_rules to match coverage of other GHAS functions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
repo.git.stash()withsubprocess.run()for all four stash operations so they work correctly in git linked worktreesGIT_DIR,GIT_WORK_TREE,GIT_INDEX_FILE,GIT_COMMON_DIRfrom the subprocess environment, matching what a plain shellgit stashseesℹ️) rather than an errorRoot cause
GitPython's
repo.git.stash()fails with exit code 1 in linked worktrees even when changes exist. Git interprets the working tree as clean because GitPython may setGIT_DIRor run in a context that prevents git from finding the correct index/work-tree relationship. The plain shellgit stashworks because it inherits no such env vars and auto-detects everything from the.gitfile in the worktree directory.Test plan
test_git_stash_push_returns_success_when_changes_exist— basic happy pathtest_git_stash_push_with_message/test_git_stash_push_with_message_containing_spacestest_git_stash_push_returns_info_when_nothing_to_stash— exit-code-1 is not an errortest_git_stash_push_in_worktree— regression test for git_stash_push fails with exit code 1 even when unstaged changes exist #189; creates a linked worktree, modifies a file, callsgit_stash_pushand asserts✅test_git_stash_push_include_untrackedtest_git_stash_list_empty/test_git_stash_list_shows_stashtest_git_stash_pop_latest/test_git_stash_drop_latest/test_git_stash_drop_by_idCloses #189
🤖 Generated with Claude Code