Skip to content

Bitbucket Integration

Bitbucket doesn’t have first-class Copybara support, but you can integrate it using git.origin(), git.destination(), and additional tooling for PR creation.

origin = git.origin(
url = "https://bitbucket.org/workspace/repo.git",
ref = "main",
)

For SSH access:

origin = git.origin(
url = "git@bitbucket.org:workspace/repo.git",
ref = "main",
)
destination = git.destination(
url = "https://bitbucket.org/workspace/repo.git",
push = "main",
)
core.workflow(
name = "sync",
origin = git.origin(
url = "https://github.com/org/source-repo.git",
ref = "main",
),
destination = git.destination(
url = "https://bitbucket.org/workspace/dest-repo.git",
push = "copybara/sync", # Feature branch for PR
),
authoring = authoring.overwrite("Sync Bot <sync@example.com>"),
transformations = [
metadata.squash_notes(),
],
mode = "SQUASH",
)

Create an App Password in Bitbucket Cloud:

  1. Go to Personal settingsApp passwords
  2. Create with permissions: repository:write, pullrequest:write
  3. Use in URL: https://username:app_password@bitbucket.org/workspace/repo.git
Terminal window
# Configure credentials
git config --global credential.helper store
echo "https://username:app_password@bitbucket.org" >> ~/.git-credentials

Personal Access Tokens (Server/Data Center)

Section titled “Personal Access Tokens (Server/Data Center)”

For Bitbucket Server/Data Center:

  1. Go to ProfileManage accountPersonal access tokens
  2. Create token with repository:write permission
  3. Use as password with any username
Terminal window
echo "https://username:personal_token@bitbucket.company.com" >> ~/.git-credentials

#!/bin/bash
# Requires: pip install atlassian-python-api
# Run Copybara first
java -jar copybara.jar migrate copy.bara.sky sync
# Create PR using Python script
python3 << 'EOF'
from atlassian.bitbucket import Cloud
bitbucket = Cloud(
url="https://api.bitbucket.org",
username="your-username",
password="your-app-password"
)
workspace = bitbucket.workspaces.get("your-workspace")
repo = workspace.repositories.get("your-repo")
repo.pullrequests.create(
title="Sync from source repository",
source_branch="copybara/sync",
destination_branch="main",
description="Automated sync via Copybara"
)
EOF
#!/bin/bash
set -e
WORKSPACE="your-workspace"
REPO="your-repo"
SOURCE_BRANCH="copybara/sync"
TARGET_BRANCH="main"
# Run Copybara
java -jar copybara.jar migrate copy.bara.sky sync
# Check if branch exists
if git ls-remote --exit-code origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
# Create PR
curl -X POST \
-H "Authorization: Bearer $BITBUCKET_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Sync from source repository\",
\"source\": {
\"branch\": {\"name\": \"$SOURCE_BRANCH\"}
},
\"destination\": {
\"branch\": {\"name\": \"$TARGET_BRANCH\"}
},
\"description\": \"Automated sync via Copybara.\n\nPlease review and merge.\"
}" \
"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pullrequests"
fi

Use Copybara’s http.endpoint() for fully integrated PR creation:

def _create_bitbucket_pr(ctx):
"""Create a Bitbucket Cloud pull request."""
workspace = "your-workspace"
repo = "your-repo"
response = ctx.destination.post(
url = "https://api.bitbucket.org/2.0/repositories/{}/{}/pullrequests".format(
workspace, repo
),
headers = {"Content-Type": "application/json"},
body = http.json({
"title": "Sync from source: " + ctx.refs[0].ref,
"source": {"branch": {"name": "copybara/sync"}},
"destination": {"branch": {"name": "main"}},
"description": "Automated sync via Copybara.\n\nSource: " + ctx.refs[0].url,
}),
)
if response.status_code == 201:
return ctx.success()
elif response.status_code == 409:
# PR already exists
ctx.console.info("PR already exists, skipping creation")
return ctx.success()
else:
return ctx.error("Failed to create PR: " + str(response.status_code))
core.feedback(
name = "create_bitbucket_pr",
origin = git.github_trigger(
url = "https://github.com/org/source-repo",
events = ["push"],
),
destination = http.endpoint(
hosts = [
http.host(
host = "api.bitbucket.org",
auth = http.bearer_auth(
creds = credentials.static_secret("bb_token", "BITBUCKET_TOKEN"),
),
),
],
),
actions = [core.action(impl = _create_bitbucket_pr)],
)

name: Sync to Bitbucket
on:
push:
branches: [main]
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Download Copybara
run: |
curl -fsSL -o copybara.jar \
https://github.com/google/copybara/releases/latest/download/copybara_deploy.jar
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Run Copybara
env:
BITBUCKET_USER: ${{ secrets.BITBUCKET_USER }}
BITBUCKET_TOKEN: ${{ secrets.BITBUCKET_TOKEN }}
run: |
# Configure Bitbucket credentials
echo "https://$BITBUCKET_USER:$BITBUCKET_TOKEN@bitbucket.org" >> ~/.git-credentials
git config --global credential.helper store
# Run Copybara
java -jar copybara.jar migrate copy.bara.sky sync --ignore-noop
- name: Create Bitbucket PR
if: success()
env:
BITBUCKET_TOKEN: ${{ secrets.BITBUCKET_TOKEN }}
run: |
# Check if PR already exists
EXISTING_PR=$(curl -s \
-H "Authorization: Bearer $BITBUCKET_TOKEN" \
"https://api.bitbucket.org/2.0/repositories/${{ vars.BB_WORKSPACE }}/${{ vars.BB_REPO }}/pullrequests?q=source.branch.name=\"copybara/sync\"" \
| jq '.size')
if [ "$EXISTING_PR" = "0" ]; then
curl -X POST \
-H "Authorization: Bearer $BITBUCKET_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Sync from GitHub",
"source": {"branch": {"name": "copybara/sync"}},
"destination": {"branch": {"name": "main"}}
}' \
"https://api.bitbucket.org/2.0/repositories/${{ vars.BB_WORKSPACE }}/${{ vars.BB_REPO }}/pullrequests"
else
echo "PR already exists, skipping creation"
fi

If your source is in Bitbucket and you’re syncing to another repo:

bitbucket-pipelines.yml
image: eclipse-temurin:21-jdk
pipelines:
branches:
main:
- step:
name: Sync with Copybara
script:
- curl -fsSL -o copybara.jar https://github.com/google/copybara/releases/latest/download/copybara_deploy.jar
- git config --global user.name "Bitbucket Pipelines"
- git config --global user.email "pipelines@bitbucket.org"
- echo "https://x-access-token:$DEST_TOKEN@bitbucket.org" >> ~/.git-credentials
- git config --global credential.helper store
- java -jar copybara.jar migrate copy.bara.sky sync --ignore-noop

Base URL: https://api.bitbucket.org/2.0

EndpointMethodDescription
/repositories/{workspace}/{repo}/pullrequestsPOSTCreate PR
/repositories/{workspace}/{repo}/pullrequestsGETList PRs
/repositories/{workspace}/{repo}/pullrequests/{id}GETGet PR
/repositories/{workspace}/{repo}/pullrequests/{id}PUTUpdate PR
/repositories/{workspace}/{repo}/pullrequests/{id}/mergePOSTMerge PR

Authentication: Bearer token or Basic auth with App Password

Documentation: Bitbucket Cloud REST API

Base URL: https://your-server.com/rest/api/1.0

EndpointMethodDescription
/projects/{project}/repos/{repo}/pull-requestsPOSTCreate PR
/projects/{project}/repos/{repo}/pull-requestsGETList PRs
/projects/{project}/repos/{repo}/pull-requests/{id}GETGet PR
/projects/{project}/repos/{repo}/pull-requests/{id}PUTUpdate PR
/projects/{project}/repos/{repo}/pull-requests/{id}/mergePOSTMerge PR

Authentication: Bearer token with Personal Access Token

Documentation: Bitbucket Server REST API


fatal: Authentication failed for 'https://bitbucket.org/...'

Solutions:

  • Verify App Password has repository:write permission
  • Check username format (use Atlassian account email for Cloud)
  • Ensure token hasn’t expired
{ "error": { "message": "source branch not found" } }

Solutions:

  • Verify Copybara pushed the branch successfully
  • Check branch name matches exactly
  • Ensure fetch-depth: 0 in CI checkout
{
"error": {
"message": "Only one pull request may be open for a given source and target branch"
}
}

Solutions:

  • Check for existing PRs before creating
  • Update the existing PR instead
  • Use the wrapper script pattern that handles this case