Git Sync
3. Synchronization & Pull Requests
Creating Branches, Commits & Pull Requests
Creating Branches, Commits & Pull Requests
At the core of tokomono/git-sync is the Tokomono\GitSync\Contracts\GitProviderInterface contract. You can inject this interface into any controller, job, listener, or Artisan command to create a branch, commit one or more files, and open a Pull Request in three calls.
GitProviderInterface Contract
namespace Tokomono\GitSync\Contracts;
interface GitProviderInterface
{
/**
* Create a new branch from a source branch.
*/
public function createBranch(string $sourceBranch, string $newBranch): void;
/**
* Commit a file to the specified branch.
*/
public function commitFile(string $branch, string $filePath, string $content, string $commitMessage): void;
/**
* Create a pull request.
*
* @return string The URL of the created Pull Request.
*/
public function createPullRequest(string $sourceBranch, string $targetBranch, string $title, string $description): string;
}
Complete End-to-End Usage Example
Below is a complete example showing how to validate the target branch against config('git-sync.target_branches'), create a sync branch, commit a modified PHP class file, open a Pull Request, and record the Pull Request in the git_sync_pull_requests table:
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Tokomono\GitSync\Contracts\GitProviderInterface;
class SyncWorkflowToGitAction
{
public function __construct(
protected GitProviderInterface $gitProvider,
) {}
public function execute(string $targetBranch, string $relativeFilePath, string $phpContent, string $workflowName): string
{
$allowedBranches = config('git-sync.target_branches', ['main', 'master', 'develop', 'staging']);
if (! in_array($targetBranch, $allowedBranches, true)) {
throw new \InvalidArgumentException("Target branch [{$targetBranch}] is not allowed.");
}
$sourceBranch = 'ai-sync/'.Str::slug($workflowName).'-'.now()->format('Ymd-His');
$title = "chore(ai-suite): update {$workflowName} workflow definition";
$description = "Automated Pull Request generated by `tokomono/git-sync`.\n\n- **Updated file:** `{$relativeFilePath}`\n- **Target branch:** `{$targetBranch}`";
// 1. Create a new branch from the target branch
$this->gitProvider->createBranch($targetBranch, $sourceBranch);
// 2. Commit the updated file content to the new branch
$this->gitProvider->commitFile(
branch: $sourceBranch,
filePath: $relativeFilePath,
content: $phpContent,
commitMessage: $title,
);
// 3. Open the Pull Request and receive the PR URL
$prUrl = $this->gitProvider->createPullRequest(
sourceBranch: $sourceBranch,
targetBranch: $targetBranch,
title: $title,
description: $description,
);
// 4. Record the Pull Request in git_sync_pull_requests
DB::table('git_sync_pull_requests')->insert([
'target_branch' => $targetBranch,
'source_branch' => $sourceBranch,
'title' => $title,
'provider' => config('git-sync.default_provider', 'github'),
'pr_url' => $prUrl,
'status' => 'open',
'created_at' => now(),
'updated_at' => now(),
]);
return $prUrl;
}
}
Testing Git Sync with Http::fake()
Because both GitHubProvider and BitbucketProvider use Laravel's Http facade (Illuminate\Support\Facades\Http), you can test your entire Git Sync flow in PHPUnit without making real network calls:
use Illuminate\Support\Facades\Http;
use Tokomono\GitSync\Contracts\GitProviderInterface;
public function test_it_creates_branch_commits_file_and_opens_github_pr(): void
{
config()->set('git-sync.default_provider', 'github');
config()->set('git-sync.providers.github', [
'token' => 'ghp_test_token',
'repository' => 'tokomono/demo-app',
]);
Http::fake([
'https://api.github.com/repos/tokomono/demo-app/git/ref/heads/main' => Http::response([
'object' => ['sha' => 'abc123456789'],
], 200),
'https://api.github.com/repos/tokomono/demo-app/git/refs' => Http::response([
'ref' => 'refs/heads/ai-sync/update-workflow',
], 201),
'https://api.github.com/repos/tokomono/demo-app/contents/*' => Http::sequence()
->push(['sha' => 'existing_file_sha'], 200)
->push(['content' => ['path' => 'app/Ai/Workflows/OrderWorkflow.php']], 200),
'https://api.github.com/repos/tokomono/demo-app/pulls' => Http::response([
'html_url' => 'https://github.com/tokomono/demo-app/pull/15',
], 201),
]);
$provider = app(GitProviderInterface::class);
$provider->createBranch('main', 'ai-sync/update-workflow');
$provider->commitFile('ai-sync/update-workflow', 'app/Ai/Workflows/OrderWorkflow.php', '<?php // updated', 'Update workflow');
$prUrl = $provider->createPullRequest('ai-sync/update-workflow', 'main', 'Update OrderWorkflow', 'Synced from staging');
$this->assertSame('https://github.com/tokomono/demo-app/pull/15', $prUrl);
}