Write operations
All writes take typed input objects and require an authenticated provider — an unauthenticated one throws InvalidCredentialsException before any request is sent:
use RoundlyConsulting\Git\Dto\Input\{NewRepository, NewBranch, NewFile, NewPullRequest, NewComment, NewRelease, NewTag};
$created = Git::github()->createRepository(new NewRepository(name: 'acme', private: true, autoInit: true));
$repo = Git::github()->repo('acme/acme');
$repo->createBranch(new NewBranch('feature/ci', 'main')); // the created branch ref
$commit = $repo->createFile(new NewFile('ci.yml', '...', 'Add CI', 'feature/ci')); // Commit
$pr = $repo->createPullRequest(new NewPullRequest('Add CI', 'feature/ci', 'main', body: 'Adds the CI workflow.'));
$repo->pullRequest($pr->number)->comment('LGTM'); // or $repo->comment(new NewComment($pr->number, 'LGTM'))
$repo->createRelease(new NewRelease('v1.0', 'First release'));
$repo->createTag(new NewTag('v1.0.1', 'main')); // a branch, tag or commit shaUpdating a file
updateFile() replaces an existing file on a branch. Read it first to get the blob sha the update needs:
use RoundlyConsulting\Git\Dto\Input\UpdatedFile;
$repo = Git::github()->repo('acme/acme');
$current = $repo->contents('ci.yml', ref: 'feature/ci');
$commit = $repo->updateFile(new UpdatedFile(
path: 'ci.yml',
content: $newYaml,
message: 'Tune CI',
branch: 'feature/ci',
sha: $current->sha, // the blob being replaced
));Input objects
Every input validates itself in its constructor and throws InvalidArgumentException, so a malformed write fails at the call site instead of as a forge 422:
| Input | Constructor | Rejected locally when |
|---|---|---|
NewRepository | name, private = false, description, owner, template, autoInit = false, defaultBranch | Name is empty, template isn’t owner/repo, or defaultBranch is set without autoInit or a template. |
NewBranch | name, fromRef | Either is empty. |
NewFile | path, content, message, branch | Path, message or branch is empty. |
UpdatedFile | path, content, message, branch, sha | Path, message, branch or sha is empty. |
NewPullRequest | title, head, base, body = null | Title, head or base is empty. |
NewComment | number, body, target = null | Body is empty. |
NewRelease | tagName, name, body, draft = false, prerelease = false | Tag name is empty. |
NewTag | name, ref | Either is empty. |
NewWebhook | url, events = ['push'], secret, active = true | URL is empty or no event is given. |
NewReview | event, body, comments = [] | A non-approval review has no body. |
NewReviewComment | path, line, body, side = Right, startLine | Path or body is empty, a line is below 1, or startLine isn’t before line. |
Worth knowing
- Writes are never retried — a 5xx on a write may already have landed, and a second POST would open a second pull request.
- createBranch() branches from fromRef and returns the new branch — its full ref on GitHub, its name on GitLab.
- NewTag’s ref may be a branch, a tag or a sha. GitHub’s tag endpoint takes only a commit sha, so a branch or tag is first resolved to the commit it points at (a full sha is used as is).
- File writes — createFile() and updateFile() — return the commit they made, with its real sha, author and date, on GitHub and GitLab alike.
- For webhooks on your own endpoint, prefer the idempotent repo($path)->webhooks() helper — see Webhooks.
Comment targets
GitHub comments on issues and pull requests through one endpoint, but GitLab numbers issues and merge requests separately, so a GitLab comment must say which with a CommentTarget. An untargeted GitLab comment throws InvalidArgumentException rather than guessing; ->pullRequest($n)->comment() sets the target for you:
use RoundlyConsulting\Git\Dto\Input\NewComment;
use RoundlyConsulting\Git\Enums\CommentTarget;
$repo = Git::gitlab()->repo('acme/api');
$repo->comment(new NewComment(3, 'Thanks', target: CommentTarget::Issue)); // issue #3
$repo->comment(new NewComment(7, 'LGTM', target: CommentTarget::PullRequest)); // merge request !7
$repo->pullRequest(7)->comment('LGTM'); // sets the target for youBitbucket comments on pull requests only and refuses CommentTarget::Issue with FeatureNotSupportedException.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.