Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c2c4cb7
Add Bitbucket adapter
HarshMN2345 Jul 29, 2026
5690b9e
Fix Bitbucket webhook ids and multi-ref pushes
HarshMN2345 Jul 29, 2026
ca2c1fa
Return Bitbucket's webhook UUID from createWebhook
HarshMN2345 Jul 30, 2026
fe8e115
Move the Bitbucket tests onto the shared Base contract
HarshMN2345 Jul 30, 2026
1eef616
Fold the presigned-url skip into Base's generic test
HarshMN2345 Jul 30, 2026
2ffe0a4
Promote getEvents() to the shared contract, wrapping getEvent() by de…
HarshMN2345 Jul 30, 2026
a3c776f
Trim the Bitbucket test to what only it can cover
HarshMN2345 Jul 30, 2026
aa82e10
Document getEvents() for batched webhook deliveries
HarshMN2345 Jul 30, 2026
dbd51fb
Cover owner lookup without a repository id in Base
HarshMN2345 Jul 30, 2026
a131258
fix: resolve Bitbucket getOwnerName() via /user/workspaces
HarshMN2345 Jul 30, 2026
9d62da5
fix: don't let a missing file crash the worker in getRepositoryContent()
HarshMN2345 Jul 30, 2026
d7f53da
feat: implement getRepositoryPresignedUrl() for Bitbucket
HarshMN2345 Jul 30, 2026
070f703
fix: correct Bitbucket archive format, guard webhook uuid, cover pres…
HarshMN2345 Jul 31, 2026
59dd3d9
fix: stop BitbucketTest::testGetUser from shadowing Base's test
HarshMN2345 Jul 31, 2026
ed950e9
fix: remove Bitbucket-only public methods that broke the generic cont…
HarshMN2345 Jul 31, 2026
74553e6
fix: stop testCreateAndDeleteWebhook from doubling webhook creation
HarshMN2345 Jul 31, 2026
103302a
fix: match Base's existing skip-helper pattern for the webhook test
HarshMN2345 Jul 31, 2026
b53fa7e
fix: don't run webhook creation against a token that can't create one
HarshMN2345 Jul 31, 2026
10199a7
style: trim over-explained comments down to one line each
HarshMN2345 Jul 31, 2026
130ee16
fix: move createWebhook/deleteWebhook onto Adapter, next to their scope
HarshMN2345 Jul 31, 2026
3dde3de
fix: don't orphan a webhook when its create response lacks a uuid
HarshMN2345 Jul 31, 2026
987f592
fix: pick the newest webhook by created_at, dedupe repeated logic
HarshMN2345 Jul 31, 2026
f973c5b
fix: refuse to guess between webhooks sharing a url
HarshMN2345 Jul 31, 2026
7d484e1
refactor: remove duplication, dead code and over-explaining from the …
HarshMN2345 Jul 31, 2026
247a722
fix: return the routable slug and a real blob sha; ride Base for stat…
HarshMN2345 Jul 31, 2026
4292981
refactor: scope the PR back to Bitbucket
HarshMN2345 Aug 3, 2026
c453a03
ci: mint the Bitbucket test token per run instead of storing one
HarshMN2345 Aug 3, 2026
ec1ec93
ci: report what Bitbucket says when minting a token fails
HarshMN2345 Aug 3, 2026
9369f7b
fix: say what Bitbucket objected to when creating a repository fails
HarshMN2345 Aug 3, 2026
e737a29
docs: drop the token-minting walkthrough from CONTRIBUTING
HarshMN2345 Aug 3, 2026
6e5881a
test: only read the constant a subclass restates through static::
HarshMN2345 Aug 3, 2026
aa49260
fix: don't name a branch Bitbucket hasn't created yet
HarshMN2345 Aug 3, 2026
1531cb7
feat: accept an Atlassian account API token as email:token
HarshMN2345 Aug 3, 2026
a5278be
ci: report which auth scheme the Bitbucket credential selects
HarshMN2345 Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/tests-external.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,35 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [gitea, forgejo, github, gitlab, gogs]
adapter: [gitea, forgejo, github, gitlab, gogs, bitbucket]

steps:
- name: Check out the repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Check Bitbucket credential shape
if: matrix.adapter == 'bitbucket'
env:
TOKEN: ${{ secrets.TESTS_BITBUCKET_ACCESS_TOKEN }}
run: |
# Reports which auth scheme the adapter will pick, never the value
if [ -z "$TOKEN" ]; then
echo "TESTS_BITBUCKET_ACCESS_TOKEN is not set"
elif printf '%s' "$TOKEN" | grep -q ':'; then
echo "credential is email:token -> Basic (${#TOKEN} chars)"
else
echo "credential is a bare token -> Bearer (${#TOKEN} chars)"
fi

- name: Start Test Stack
env:
TESTS_GITHUB_PRIVATE_KEY: ${{ secrets.TESTS_GITHUB_PRIVATE_KEY }}
TESTS_GITHUB_APP_IDENTIFIER: ${{ secrets.TESTS_GITHUB_APP_IDENTIFIER }}
TESTS_GITHUB_INSTALLATION_ID: ${{ secrets.TESTS_GITHUB_INSTALLATION_ID }}
TESTS_BITBUCKET_ACCESS_TOKEN: ${{ secrets.TESTS_BITBUCKET_ACCESS_TOKEN }}
TESTS_BITBUCKET_WORKSPACE: ${{ secrets.TESTS_BITBUCKET_WORKSPACE }}
run: |
docker compose --profile ${{ matrix.adapter }} up -d --wait --wait-timeout 900

Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,33 @@ jobs:
strategy:
fail-fast: false
matrix:
adapter: [gitea, forgejo, github, gitlab, gogs]
adapter: [gitea, forgejo, github, gitlab, gogs, bitbucket]

steps:
- name: Check out the repo
uses: actions/checkout@v4

- name: Check Bitbucket credential shape
if: matrix.adapter == 'bitbucket'
env:
TOKEN: ${{ secrets.TESTS_BITBUCKET_ACCESS_TOKEN }}
run: |
# Reports which auth scheme the adapter will pick, never the value
if [ -z "$TOKEN" ]; then
echo "TESTS_BITBUCKET_ACCESS_TOKEN is not set"
elif printf '%s' "$TOKEN" | grep -q ':'; then
echo "credential is email:token -> Basic (${#TOKEN} chars)"
else
echo "credential is a bare token -> Bearer (${#TOKEN} chars)"
fi

- name: Start Test Stack
env:
TESTS_GITHUB_PRIVATE_KEY: ${{ secrets.TESTS_GITHUB_PRIVATE_KEY }}
TESTS_GITHUB_APP_IDENTIFIER: ${{ secrets.TESTS_GITHUB_APP_IDENTIFIER }}
TESTS_GITHUB_INSTALLATION_ID: ${{ secrets.TESTS_GITHUB_INSTALLATION_ID }}
TESTS_BITBUCKET_ACCESS_TOKEN: ${{ secrets.TESTS_BITBUCKET_ACCESS_TOKEN }}
TESTS_BITBUCKET_WORKSPACE: ${{ secrets.TESTS_BITBUCKET_WORKSPACE }}
run: |
docker compose --profile ${{ matrix.adapter }} up -d --wait --wait-timeout 900

Expand Down
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ sleep 15
docker compose exec -T tests vendor/bin/phpunit --configuration phpunit.xml --testsuite <adapter>
```

Where `<adapter>` is one of: `gitea`, `forgejo`, `github`, `gitlab`, `gogs`.
Where `<adapter>` is one of: `gitea`, `forgejo`, `github`, `gitlab`, `gogs`, `bitbucket`.

For example, to run Gitea tests:

Expand All @@ -100,6 +100,13 @@ docker compose exec -T tests vendor/bin/phpunit --configuration phpunit.xml --te

The `github` adapter does not require any local services — only the GitHub secrets (`TESTS_GITHUB_PRIVATE_KEY`, `TESTS_GITHUB_APP_IDENTIFIER`, `TESTS_GITHUB_INSTALLATION_ID`) as environment variables.

The `bitbucket` adapter runs against Bitbucket Cloud, which has no self-hostable image, so it needs credentials instead of a local service:

- `TESTS_BITBUCKET_ACCESS_TOKEN` — a credential with read and write access to repositories, pull requests and webhooks: either a bearer token (OAuth 2.0, workspace or repository) or an Atlassian account API token given as `email:token`
- `TESTS_BITBUCKET_WORKSPACE` — workspace the test repositories are created in; defaults to the token owner's own workspace

The workspace needs at least one project, since Bitbucket assigns every new repository to one. Without these variables the suite is skipped, the same way the `github` one is.

## Adding A New Adapter

You can follow our [Adding new VCS Adapter](docs/add-new-vcs-adapter.md) tutorial to add a new VCS adapter like GitLab, Bitbucket etc. in this library.
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ services:
- TESTS_GITHUB_PRIVATE_KEY
- TESTS_GITHUB_APP_IDENTIFIER
- TESTS_GITHUB_INSTALLATION_ID
- TESTS_BITBUCKET_ACCESS_TOKEN
- TESTS_BITBUCKET_WORKSPACE
- TESTS_GITEA_URL=http://gitea:3000
- TESTS_REQUEST_CATCHER_URL=http://request-catcher:5000
- TESTS_FORGEJO_URL=http://forgejo:3000
Expand Down
17 changes: 17 additions & 0 deletions docs/add-new-vcs-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ $vcs->initializeVariables($installationId, $privateKey, $appId);

Only include dependencies strictly necessary for the adapter, preferably official PHP libraries, if available.

#### Webhook deliveries describing more than one event

`getEvent()` returns the single event a payload describes, which is all most
providers ever send. A provider that batches several refs into one delivery
should override `getEvents()` as well, so a consumer can read all of them:

```php
public function getEvents(string $event, string $payload): array
{
// one entry per ref the delivery touched
}
```

The default `getEvents()` wraps `getEvent()`, so an adapter that never batches
needs no override. Consumers that must not miss a ref should call `getEvents()`
rather than `getEvent()` — the latter reports only the first event of a batch.

### Testing with Docker 🛠️

The existing test suite is helpful when developing a new VCS adapter. Use official Docker images from trusted sources. Add new tests for your new VCS adapter in `tests/VCS/Adapter/VCSTest.php` test class. The specific `docker-compose` command for testing can be found in the [README](/README.md#tests).
Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@
<testsuite name="gogs">
<file>./tests/VCS/Adapter/GogsTest.php</file>
</testsuite>
<testsuite name="bitbucket">
<file>./tests/VCS/Adapter/BitbucketTest.php</file>
</testsuite>
</testsuites>
</phpunit>
20 changes: 20 additions & 0 deletions src/VCS/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,26 @@ public function validateWebhookEvent(string $payload, string $signature, string
*/
abstract public function getEvent(string $event, string $payload): array;

/**
* Parses a webhook payload into every event it describes.
*
* Default wraps getEvent(), the one event every provider's payload can
* describe. Override for a provider whose payload can batch several (e.g.
* Bitbucket, which reports every ref a push touched in one delivery).
*
* @param string $event Type of event: push, pull_request etc
* @param string $payload The webhook payload received from Git provider
* @return array<array<mixed>> Parsed payloads as json objects
*/
public function getEvents(string $event, string $payload): array
{
$parsed = $this->getEvent($event, $payload);

// An event the adapter doesn't report describes nothing, so report
// nothing rather than one empty event
return $parsed === [] ? [] : [$parsed];
}

/**
* HTTP header name carrying the webhook event type (e.g. 'x-github-event').
*/
Expand Down
6 changes: 4 additions & 2 deletions src/VCS/Adapter/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ abstract public function createPullRequest(string $owner, string $repositoryName
* @param string $url Webhook URL to send events to
* @param string $secret Webhook secret for signature validation
* @param array<string> $events Events to trigger the webhook
* @return int Webhook ID
* @return int|string Webhook ID, as the provider identifies it: an int on
* the providers that number their hooks, a string where
* they don't (Bitbucket identifies them by UUID)
*/
abstract public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int;
abstract public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int|string;


/**
Expand Down
Loading
Loading