particle-academy/fancy-git
particle-academy/fancy-git
Framework-agnostic PHP Git engine with normalized provider contracts and proposal-first mutations.
$
composer require particle-academy/fancy-gitWhy
Anything that automates Git ends up writing the same two layers twice. First a shell of
It is also built for Human+ UX, and that shows up as a design constraint rather than a feature: every mutation takes a
exec('git status --porcelain') calls with hand-rolled parsing, which works until a path has a space in it or the porcelain format shifts. Then, separately, a REST client per host — because GitHub calls it a pull request, GitLab a merge request, and Bitbucket a pull request with an entirely different payload — so supporting a second host means writing the feature again. fancy-git is that work done once, in two languages: local operations over the installed git binary, and a provider-neutral contract for the hosted side, so one code path serves GitHub, GitLab and Bitbucket.It is also built for Human+ UX, and that shows up as a design constraint rather than a feature: every mutation takes a
propose flag. Called with it, the operation returns an OperationProposal describing exactly what it would run instead of running it. An agent can therefore stage, branch, or push through the same API a human uses, and a human confirms before anything touches the repository — trust-but-verify in the contract, not bolted on by the caller.What
The family is nine packages in a deliberate shape: a matched PHP + TypeScript core, a React UI, and a per-host adapter pair.
The engine —
The hosted contract —
The adapters — six packages, one pair per host:
The surface —
The engine —
particle-academy/fancy-git (PHP) and @particle-academy/fancy-git (TypeScript) are twins, not a port and an afterthought. Both expose a GitRepository over the installed binary: info(), status(), log(), diff(), branches(), and the mutations stage() / unstage() / checkout() / fetch() / pull() / push(), each taking that propose flag. Invocation goes through a process runner (Symfony Process on PHP), never a shell string, so a branch named --upload-pack=… is an argument rather than an injection.The hosted contract —
GitProvider normalises the three hosts to one vocabulary: repository(), listReviews(), getReview(), createReview(), compare(), checks(). A pull request and a merge request are both a Review, with the host's extras preserved under extensions rather than flattened away. Adapters register on a ProviderRegistry, and identify() maps a remote URL back to the provider that owns it — so code written against the contract works on a host it has never seen.The adapters — six packages, one pair per host:
fancy-git-github-{php,js}, fancy-git-gitlab-{php,js}, fancy-git-bitbucket-{php,js}. You install only the hosts you actually talk to; the core carries no HTTP client for the other two.The surface —
@particle-academy/fancy-git-ui ships eight controlled React components over that data: <RepositoryBrowser>, <WorkingTree>, <CommitHistory>, <DiffViewer>, <BranchPicker>, <ReviewList>, <CommitComposer> and <CreateReviewForm>. Every one is fully controlled and provider-neutral — it renders what you pass and emits intents, holding no Git state of its own, which is what lets an agent drive the same surface a person is looking at.How
Pick the runtime that executes your workflow, then add the hosts you use. On PHP:
Local operations read the same on both sides:
For the hosted side, register the adapters you installed and let the remote choose:
composer require particle-academy/fancy-git particle-academy/fancy-git-github. On Node: npm install @particle-academy/fancy-git @particle-academy/fancy-git-github. The React surface is additional and orthogonal — npm install @particle-academy/fancy-git-ui — because a Laravel app still renders its editor in a browser.Local operations read the same on both sides:
$repository = new GitRepository(base_path()); $status = $repository->status(); in PHP, const status = await new GitRepository(process.cwd()).status() in TypeScript. To let an agent act without letting it act unsupervised, pass the flag: $repository->push(propose: true) returns an OperationProposal you can render for a human to approve, then re-run without it once they do.For the hosted side, register the adapters you installed and let the remote choose:
$registry = (new ProviderRegistry)->register(new GitHubProvider($token)); [$provider, $ref] = $registry->identify($remote); — from there $provider->listReviews($ref) is the same call whether that remote points at GitHub, GitLab or Bitbucket. And if you would rather drive all of this from a workflow than from code, the marketplace ships five PR-lifecycle nodes built on exactly this contract: npx fancy-cli add node @particle-academy/git_pr_open.API surface
This package renders no UI surface — it is the hooks / APIs / server-side tooling described above.
Renders no UI. A supporting package with no rendered components, so there are no live demos — reach for the README + Changelog for the full reference, and the Issues link to file feedback.
Fancy Git for PHP
Framework-agnostic local Git operations and normalized provider contracts.
use FancyGit\GitRepository;
$repository = new GitRepository(getcwd());
$status = $repository->status();
The core uses Symfony Process for safe, portable invocation of the installed Git binary. GitHub, GitLab, and Bitbucket clients live in separate adapter packages.
