Skip to content

TigerGit transports — ApiTransport (commit spike) + ShellTransport (TIGER-364) - #357

Closed
WebTigers wants to merge 3 commits into
feat/tiger-334-git-module-phase1from
feat/tiger-364-git-transports
Closed

WebTigers wants to merge 3 commits into
feat/tiger-334-git-module-phase1from
feat/tiger-364-git-transports

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

Epic TIGER-334 — Phase 2 (TIGER-364), the ApiTransport commit spike

Leads Phase 2 with the module's biggest technical risk: creating a commit with no local git repo. Proves Tiger_Git_Transport_Api::commit() through the GitHub Git Data API:

  1. read the branch head (ref) + its base tree
  2. POST a blob per changed file (base64); a null-sha entry per deleted file
  3. POST a new tree (on base_tree) then a new commit (parent = the old head)
  4. fast-forward the branch ref (force:false) — a branch that moved under us is reported NON_FAST_FORWARD, never clobbered

With the API transport there's no local index, so commit() writes straight to the remote branch in one flow and push() is a verified no-op (committing is pushing). Also built: branches(), createBranch() (refuses the default branch). status/diff/switchBranch/pull/tag/listReleases/fetchArchive throw NOT_IMPLEMENTED — the rest of TIGER-364.

Safety / design

  • HTTP is a seam (setHttp) → the whole flow is unit-tested against canned GitHub responses, no network.
  • Token resolved per request via Tiger_Git_Credential (never held); every call pinned to api.github.com; redirects off.
  • Errors map to stable, secret-free codes: 401→CREDENTIAL_INVALID, 403 rate→RATE_LIMITED, 404→INVALID_REF, 422 "fast forward"→NON_FAST_FORWARD.

Tests

10 unit (29 across the Git suite): the four-step sequence and payloads asserted (blob = base64 file content; tree builds on base_tree, deletion = null sha; commit parents the old head; ref update force:false), plus NON_FAST_FORWARD, missing-credential (no API call made), nothing-to-commit, empty-message, createBranch, branches, push no-op, NOT_IMPLEMENTED.

Stacking + next

🤖 Generated with Claude Code

https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ

WebTigers and others added 2 commits October 21, 2026 13:39
…API, no .git (TIGER-364)

Phase 2 leads with the module's biggest risk: committing with no local git repo. Tiger_Git_Transport_Api
implements the commit path through the GitHub Git Data API:

  1. read the branch head (ref) + its base tree
  2. POST a blob per changed file (base64); a null-sha entry per deleted file
  3. POST a new tree (on base_tree) then a new commit (parent = the old head)
  4. fast-forward the branch ref (force:false) — a branch that moved under us is reported
     NON_FAST_FORWARD, never clobbered

With the API transport there is no local index, so commit() writes to the remote in one flow and push()
is a verified no-op (committing IS pushing). Also implemented: branches(), createBranch() (refuses the
default branch). status/diff/switch/pull/tag/listReleases/fetchArchive throw NOT_IMPLEMENTED — the rest
of TIGER-364.

The HTTP layer is a seam (setHttp), the token is resolved per request via Tiger_Git_Credential (never
held), and every call is pinned to api.github.com. Errors map to stable, secret-free codes
(401->CREDENTIAL_INVALID, 403 rate->RATE_LIMITED, 404->INVALID_REF, 422 'fast forward'->NON_FAST_FORWARD).

Tests: 10 unit (29 across the Git suite) — the full four-step sequence + payloads asserted against canned
GitHub responses (no network), plus NON_FAST_FORWARD, missing-credential (no call made), nothing-to-commit,
empty-message, createBranch, branches, push no-op, and the NOT_IMPLEMENTED guards. CAPABILITIES.md regenerated.

Built on the Phase-1 substrate (PR #356); rebase onto main once that merges. A live-GitHub integration
test (real throwaway repo + token) is the Testing-column gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ
The counterpart to ApiTransport, behind the same interface: git operations driven by the `git` binary
over a real .git working tree, for a host that has git and can spawn a subprocess. Host-agnostic (works
against any git remote, not just GitHub).

Implemented: status (branch + porcelain changes + ahead/behind vs upstream), diff (numstat, size-capped),
commit (stage -> -c author commit -> rev-parse; NOTHING_TO_COMMIT when empty), branches, createBranch
(off HEAD, refuses the default), switchBranch (refuses a dirty tree -> DIRTY_TREE), push (origin, NO
--force ever -> NON_FAST_FORWARD on rejection), pull (--ff-only), tag (annotated + pushed to origin).
listReleases/fetchArchive are a provider-API concern (GitHub adapter routes them to ApiTransport) ->
NOT_IMPLEMENTED.

Safety, structural: git is invoked via proc_open with an ARGUMENT ARRAY (no shell string, no
metacharacter interpretation); only a fixed subcommand set runs; every path is resolved under the module
root first; ref/branch/tag names are validated (no leading '-', no '..'); a token is injected through an
inline credential.helper that reads GIT_TOKEN from the ENV, so the secret is never in argv (ps-visible)
nor written to .git/config. A command-runner seam (setRunner) allows stubbing.

Tests: 12, proven END TO END against a real local repo + a bare file:// "remote", with a second clone
used to force a genuine NON_FAST_FORWARD (push refused, never forced) and to verify push/tag actually
reach the remote. Skipped where git is absent. Full Git suite 41 green; full unit suite 1172 green.
CAPABILITIES.md regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ
@WebTigers WebTigers changed the title ApiTransport commit spike — commits via the GitHub API, no .git (TIGER-364) TigerGit transports — ApiTransport (commit spike) + ShellTransport (TIGER-364) Oct 21, 2026
@WebTigers

Copy link
Copy Markdown
Owner Author

Added ShellTransport (the git-binary path) on top of the ApiTransport commit spike. It implements far more of the interface (status/diff/commit/branches/createBranch/switchBranch/push/pull/tag), is host-agnostic, and is proven end-to-end against a real local repo + a bare file:// remote — including a genuine NON_FAST_FORWARD (forced via a second clone; push refused, never --forced) and verifying push/tag reach the remote. Token auth is injected via an inline credential.helper reading GIT_TOKEN from the env (never in argv or .git/config). listReleases/fetchArchive stay NOT_IMPLEMENTED (provider-API concern). 41 Git tests, 1172 unit — all green.

…PI (TIGER-364)

Tiger_Git_Provider_GitHub composes the two transports into one object a service
uses without caring: it picks the WORKING transport for a connection (the git
binary where the host allows it, the API otherwise) for the git ops, and ALWAYS
routes the provider-API ops (listReleases/fetchArchive) to the API — because a
GitHub release is not a git concept, so even a shell connection reaches it over
the API. New Tiger_Git_Provider_Interface is the N-provider seam (GitLab/Bitbucket
land later without touching the transports).

Implements the two provider-API verbs on ApiTransport (their reason to exist):
- listReleases() — newest first, drafts excluded.
- fetchArchive() — GitHub's tarball 302 captured; the redirect host is confirmed
  against the allowlist before the CDN fetch, and our token is never forwarded to
  the signed codeload URL. send() now captures response headers for the capture.

Remaining ApiTransport verbs (status/diff/switch/pull/tag over the API) stay
NOT_IMPLEMENTED — the rest of TIGER-364.

10 new unit tests (routing proven with both seams; releases/archive incl. the
302 capture, host-allowlist refusal, bad-tag refusal). 68 Git, 1181 unit green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ
@WebTigers

Copy link
Copy Markdown
Owner Author

GitHubAdapter landed (8a172ba) — the provider layer that composes the two transports.

Tiger_Git_Provider_GitHub (implements the new Tiger_Git_Provider_Interface, the N-provider seam) picks the working transport per connection — the git binary where the host allows it, the API otherwise — for the git ops, and always routes the provider-API ops (listReleases/fetchArchive) to the API transport even on a shell connection, because a GitHub release is not a git concept (there's no git command for it). That routing is the adapter's whole reason to exist.

Implemented the two provider-API verbs on ApiTransport (their reason to exist):

  • listReleases — newest first, drafts excluded.
  • fetchArchive — GitHub answers the tarball endpoint with a 302 to codeload; the redirect is captured, its host re-checked against the allowlist, and the bytes streamed from the CDN without forwarding our token (the signed URL authenticates itself). send() now captures response headers for the capture.

Still NOT_IMPLEMENTED on the API: status/diff/switchBranch/pull/tag — the rest of TIGER-364 (a shell host reaches these via the working transport; an api-only shared host doesn't yet).

Tests: 10 new unit tests — routing proven with both seams (git-op→binary, api-op→API, release→API-even-on-shell), plus releases/archive incl. the 302 capture, the off-allowlist redirect refusal, and the bad-tag refusal. 68 Git, 1181 unit green; CAPABILITIES 244 classes.

WebTigers added a commit that referenced this pull request Oct 21, 2026
…ER-334)

The TigerGit epic, landed as one squash of the reviewed stack (#356 foundation ← #357 transports+GitHubAdapter ← #358 services+UI+MCP+reconciler).

Version a custom module in a private repo, cut a release through QA, and receive approved updates — with no local machine, no terminal, and (on shared hosting with exec disabled) no git binary. Two interchangeable transports (ShellTransport; ApiTransport with no .git), the GitHubAdapter that composes them, write-only encrypted credentials, an active-only-unique data model + audit log, the no-.git reconciler (status/diff/pull/switchBranch via a merge-base marker), the /api services (Repo/Release/Update; owner-only release capability, approved-only updates), an admin dashboard at /git/admin, and full MCP reach. Both live-GitHub gates passed (commit spike + reconciler) against real public and private repos. Ships as core 1.38.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASauLLscjqdsNqBNsx2Typ
@WebTigers

Copy link
Copy Markdown
Owner Author

Superseded by #358, which was squash-merged to main with the full stack (this PR's commits included). Closing — the changes are on main. (Kept open through review as the stacked chain #356←#357←#358.)

@WebTigers
WebTigers deleted the branch feat/tiger-334-git-module-phase1 October 21, 2026 21:46
@WebTigers WebTigers closed this Oct 21, 2026
@WebTigers
WebTigers deleted the feat/tiger-364-git-transports branch October 21, 2026 21:46
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