Skip to content

fix(#547): parse git-push dst ref, ignore redirections + tag pushes#566

Merged
atlas-apex merged 1 commit into
devfrom
fix/GH-547-push-ref-parsing
Jun 7, 2026
Merged

fix(#547): parse git-push dst ref, ignore redirections + tag pushes#566
atlas-apex merged 1 commit into
devfrom
fix/GH-547-push-ref-parsing

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Extracted DST from src:dst refspecs instead of SRCgit push upstream HEAD:feature/GH-1-foo was blocked because the hook read HEAD (left side) as the branch name. The fix makes extract_push_ref() take the right side of the colon, which is what actually lands on the remote and what the branch-naming convention applies to.
  • Strip shell redirections before token parsing — a command like git push upstream --tags 2>&1 | tail had 2> mis-parsed as a positional branch-name token. The lib now strips everything from the first redirection operator (NNN>, |) onward before extracting tokens, so 2>, >, and | can never be mistaken for branch names.
  • Tag pushes are a no-op for the branch-name validator — added is_tag_push() to _lib-extract-push-ref.sh (detects --tags, tag <name>, refs/tags/) and call it in validate-branch-name.sh before any ref extraction; exit 0 immediately. A branch-name validator has nothing to validate on a tag push.

Testing

bash .claude/hooks/tests/test_validate_branch_name_pushref.sh

28 cases total — 15 pre-existing (#194 behaviour) + 13 new (#547 cases). All pass:

PASS [#547: HEAD:valid-branch validates dst → passes]
PASS [#547: HEAD:non-conforming-dst blocks on dst]
PASS [#547: sha:dst validates dst → passes]
PASS [#547: local-branch:remote-branch validates remote-dst → passes]
PASS [#547: --tags plain → skip (no branch to validate)]
PASS [#547: --tags with 2>&1 pipe → skip (no branch to validate)]
PASS [#547: --tags before remote → skip]
PASS [#547: tag <name> keyword form → skip]
PASS [#547: refs/tags/ refspec → skip]
PASS [#547: plain 2>&1 redirect on valid push → passes]
PASS [#547: > redirect on valid push → passes]
PASS [regression: explicit ref passes (no refspec)]
PASS [regression: non-conforming explicit ref still blocks]
===================================
  PASS: 28   FAIL: 0
===================================

Refs #547


Glossary

Term Definition
Refspec A git push argument of the form <src>:<dst> (e.g. HEAD:feature/GH-1-foo) specifying the source local ref and the destination remote ref. The branch-naming convention applies only to the dst — what lands on the remote.
Shell redirection Shell operators like 2>&1, 2>, > that redirect file descriptors. They appear as tokens in the raw command string the hook receives and were previously mis-parsed as branch-name candidates.
Tag push A push that sends git tags to the remote rather than a branch ref. Indicated by --tags, tag <name>, or a refs/tags/ refspec. A branch-name validator has no rule to apply and must exit 0.
is_tag_push() New helper in _lib-extract-push-ref.sh that returns true when the command is a tag push. Called before extract_push_ref() so the tag-push guard is correct even when redirections are appended to the command.
extract_push_ref() Shared lib function (existing, in _lib-extract-push-ref.sh) that parses a git push command and returns the branch ref to validate. Updated to return the DST of a refspec and to strip redirections before parsing.

- _lib-extract-push-ref.sh: strip shell redirections (2>&1, 2>, >, |)
  before token parsing so redirect operators are never mistaken for
  branch names
- _lib-extract-push-ref.sh: for src:dst refspecs, validate the DST
  (right of colon) — not the src — because the dst is what lands on the
  remote; src may be HEAD, a SHA, or a local tracking ref that doesn't
  follow the branch-name convention
- _lib-extract-push-ref.sh: add is_tag_push() helper that returns true
  for --tags, `tag <name>`, and refs/tags/ forms
- validate-branch-name.sh: call is_tag_push() before extract_push_ref()
  and exit 0 when true — a branch-name validator has nothing to check
  on a tag push
- test_validate_branch_name_pushref.sh: 13 new cases covering all three
  fix axes plus regression guard for #194 behaviour; 28 total, all pass

Refs #547

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #566

Commit: 404ebc505fe8b269907f0f269c5df6a2c11c3f53

Summary

Fixes the git push parsing in _lib-extract-push-ref.sh across three bug axes (#547): (1) a src:dst refspec now validates the dst (what lands on the remote) instead of the src, so git push upstream HEAD:feature/GH-1-foo is no longer wrongly blocked on HEAD; (2) shell redirections/pipes are stripped before token parsing so 2>/>/| can't be mistaken for a branch name; (3) tag pushes (--tags, tag <name>, refs/tags/) short-circuit to a no-op via a new is_tag_push() guard called in validate-branch-name.sh before extraction. Adds 13 targeted test cases (28/28 pass).

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass
  • Security: Pass (no secrets/leaks; diff is shell + tests only)
  • Performance: Pass (N/A — string parsing in a hook)
  • PR Description & Glossary: Pass (5-term glossary present)
  • Summary Bullet Narrative: Pass (narrative, well above label-only threshold)
  • Technical Decisions (AgDR): N/A (bug fix to existing parser; no new dep/framework/pattern)
  • Adopter Handbooks: N/A (no handbooks loaded for this diff)

Verification performed

I checked out the PR HEAD (404ebc5) and ran the suite myself: 28 PASS / 0 FAIL, matching the reported result. Beyond the bundled tests, I probed the lib directly against edge cases not in the suite:

  • dst extractionHEAD:dstdst; +HEAD:dst (force marker) → dst; refs/heads/x:refs/heads/mainmain (dst taken, then refs/heads/ stripped). All correct.
  • redirection stripping2>&1 | tail, >, 2>, 1>, >> all stripped; &&/;/&/| separators also handled by the residual [^|;&]* grep guard.
  • is_tag_push false-positive guards — branch names containing tag/tags (feature/GH-1-add-tag-support, feature/GH-1-tags-page, tag-team) correctly classified as BRANCH, not TAG. The --tags(\s|$) and tag\s+\S anchors are tight. --follow-tags <branch> correctly stays BRANCH (it pushes the branch + tags, so branch validation should still apply).

The ${ref#*:} dst split, the redirection-stripping sed, and the is_tag_push guard are all shell-sound and BSD/macOS-portable (POSIX-only constructs, no \b in parameter expansion paths). No regressions for plain pushes, -u, --force, --delete, or no-arg pushes.

Issues Found

None blocking.

Suggestions (non-blocking)

  • nit (cosmetic): shellcheck flags SC2001 on the two echo | sed lines (lines 51, 128) suggesting ${var//search/replace}. The regex ([0-9]*[>|] with end-anchoring) isn't expressible in bash parameter expansion, so sed is the right tool — safe to leave as-is.
  • nit (theoretical): is_tag_push uses a stricter strip pattern ([>|][&>|0-9]) than extract_push_ref ([>|]). For the no-space form git push upstream --tags>/tmp/o the tag guard misses (--> not followed by &>|0-9), so it's classified BRANCH. I verified the fallthrough is harmless: extract_push_ref returns empty for it (boolean --tags consumes no positional), so the caller falls back to local-HEAD resolution — which in a real worktree is the actual branch. No false block in practice. Aligning the two sed patterns would be tidier but isn't required.

Verdict

APPROVED


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 404ebc505fe8b269907f0f269c5df6a2c11c3f53

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #566

Commit: 404ebc505fe8b269907f0f269c5df6a2c11c3f53

Summary

Fixes the git push parsing in _lib-extract-push-ref.sh across three bug axes (#547): (1) a src:dst refspec now validates the dst (what lands on the remote) instead of the src, so git push upstream HEAD:feature/GH-1-foo is no longer wrongly blocked on HEAD; (2) shell redirections/pipes are stripped before token parsing so 2>/>/| can't be mistaken for a branch name; (3) tag pushes (--tags, tag <name>, refs/tags/) short-circuit to a no-op via a new is_tag_push() guard called in validate-branch-name.sh before extraction. Adds 13 targeted test cases (28/28 pass).

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass
  • Security: Pass (no secrets/leaks; diff is shell + tests only)
  • Performance: Pass (N/A — string parsing in a hook)
  • PR Description & Glossary: Pass (5-term glossary present)
  • Summary Bullet Narrative: Pass (narrative, well above label-only threshold)
  • Technical Decisions (AgDR): N/A (bug fix to existing parser; no new dep/framework/pattern)
  • Adopter Handbooks: N/A (no handbooks loaded for this diff)

Verification performed

I checked out the PR HEAD (404ebc5) and ran the suite myself: 28 PASS / 0 FAIL, matching the reported result. Beyond the bundled tests, I probed the lib directly against edge cases not in the suite:

  • dst extractionHEAD:dstdst; +HEAD:dst (force marker) → dst; refs/heads/x:refs/heads/mainmain (dst taken, then refs/heads/ stripped). All correct.
  • redirection stripping2>&1 | tail, >, 2>, 1>, >> all stripped; &&/;/&/| separators also handled by the residual [^|;&]* grep guard.
  • is_tag_push false-positive guards — branch names containing tag/tags (feature/GH-1-add-tag-support, feature/GH-1-tags-page, tag-team) correctly classified as BRANCH, not TAG. The --tags(\s|$) and tag\s+\S anchors are tight. --follow-tags <branch> correctly stays BRANCH (it pushes the branch + tags, so branch validation should still apply).

The ${ref#*:} dst split, the redirection-stripping sed, and the is_tag_push guard are all shell-sound and BSD/macOS-portable (POSIX-only constructs, no \b in parameter expansion paths). No regressions for plain pushes, -u, --force, --delete, or no-arg pushes.

Issues Found

None blocking.

Suggestions (non-blocking)

  • nit (cosmetic): shellcheck flags SC2001 on the two echo | sed lines (lines 51, 128) suggesting ${var//search/replace}. The regex ([0-9]*[>|] with end-anchoring) isn't expressible in bash parameter expansion, so sed is the right tool — safe to leave as-is.
  • nit (theoretical): is_tag_push uses a stricter strip pattern ([>|][&>|0-9]) than extract_push_ref ([>|]). For the no-space form git push upstream --tags>/tmp/o the tag guard misses (--> not followed by &>|0-9), so it's classified BRANCH. I verified the fallthrough is harmless: extract_push_ref returns empty for it (boolean --tags consumes no positional), so the caller falls back to local-HEAD resolution — which in a real worktree is the actual branch. No false block in practice. Aligning the two sed patterns would be tidier but isn't required.

Verdict

APPROVED


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 404ebc505fe8b269907f0f269c5df6a2c11c3f53

@atlas-apex atlas-apex merged commit 2b41158 into dev Jun 7, 2026
8 checks passed
@atlas-apex atlas-apex deleted the fix/GH-547-push-ref-parsing branch June 7, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants