Azure DevOps Integration
Azure DevOps (Azure Repos) doesn’t have first-class Copybara support, but you can integrate it using git.origin(), git.destination(), and the Azure CLI or REST API for PR creation.
Basic Setup
Section titled “Basic Setup”Reading from Azure Repos
Section titled “Reading from Azure Repos”origin = git.origin( url = "https://dev.azure.com/organization/project/_git/repo", ref = "main",)For SSH access:
origin = git.origin( url = "git@ssh.dev.azure.com:v3/organization/project/repo", ref = "main",)Pushing to Azure Repos
Section titled “Pushing to Azure Repos”destination = git.destination( url = "https://dev.azure.com/organization/project/_git/repo", push = "main",)Complete Workflow
Section titled “Complete Workflow”core.workflow( name = "sync", origin = git.origin( url = "https://github.com/org/source-repo.git", ref = "main", ), destination = git.destination( url = "https://dev.azure.com/myorg/myproject/_git/dest-repo", push = "copybara/sync", # Feature branch for PR ), authoring = authoring.overwrite("Sync Bot <sync@example.com>"), transformations = [ metadata.squash_notes(), ], mode = "SQUASH",)Authentication
Section titled “Authentication”Personal Access Token (PAT)
Section titled “Personal Access Token (PAT)”Create a PAT in Azure DevOps:
- Go to User settings → Personal access tokens
- Create token with scopes:
Code (Read & Write),Pull Request (Read & Write) - Use in URL with any username
# Configure credentialsgit config --global credential.helper storeecho "https://username:$PAT@dev.azure.com" >> ~/.git-credentialsAzure CLI Authentication
Section titled “Azure CLI Authentication”# Login to Azureaz login
# Configure Azure DevOps extensionaz extension add --name azure-devopsaz devops configure --defaults organization=https://dev.azure.com/myorg project=myprojectCreating Pull Requests
Section titled “Creating Pull Requests”Method 1: Azure CLI
Section titled “Method 1: Azure CLI”The Azure CLI provides the most straightforward way to create PRs:
#!/bin/bashset -e
SOURCE_BRANCH="copybara/sync"TARGET_BRANCH="main"REPO="my-repo"
# Run Copybara firstjava -jar copybara.jar migrate copy.bara.sky sync
# Create PR using Azure CLIaz repos pr create \ --repository "$REPO" \ --source-branch "$SOURCE_BRANCH" \ --target-branch "$TARGET_BRANCH" \ --title "Sync from source repository" \ --description "Automated sync via Copybara."With Additional Options
Section titled “With Additional Options”az repos pr create \ --repository "$REPO" \ --source-branch "$SOURCE_BRANCH" \ --target-branch "$TARGET_BRANCH" \ --title "Sync from source repository" \ --description "Automated sync via Copybara." \ --reviewers "user1@company.com" "user2@company.com" \ --work-items 12345 \ --draft # Create as draft PRMethod 2: REST API with curl
Section titled “Method 2: REST API with curl”#!/bin/bashset -e
ORGANIZATION="myorg"PROJECT="myproject"REPO="myrepo"SOURCE_BRANCH="copybara/sync"TARGET_BRANCH="main"
# Run Copybarajava -jar copybara.jar migrate copy.bara.sky sync
# Get repository IDREPO_ID=$(az repos show --repository "$REPO" --query id -o tsv)
# Create PR via REST APIcurl -X POST \ -H "Authorization: Basic $(echo -n ":$AZURE_PAT" | base64)" \ -H "Content-Type: application/json" \ -d "{ \"sourceRefName\": \"refs/heads/$SOURCE_BRANCH\", \"targetRefName\": \"refs/heads/$TARGET_BRANCH\", \"title\": \"Sync from source repository\", \"description\": \"Automated sync via Copybara.\" }" \ "https://dev.azure.com/$ORGANIZATION/$PROJECT/_apis/git/repositories/$REPO_ID/pullrequests?api-version=7.0"Method 3: HTTP Endpoint (Advanced)
Section titled “Method 3: HTTP Endpoint (Advanced)”Use Copybara’s http.endpoint() for fully integrated PR creation:
def _create_azure_pr(ctx): """Create an Azure DevOps pull request.""" organization = "myorg" project = "myproject" repo = "myrepo"
response = ctx.destination.post( url = "https://dev.azure.com/{}/{}/_apis/git/repositories/{}/pullrequests".format( organization, project, repo ), headers = {"Content-Type": "application/json"}, params = {"api-version": "7.0"}, body = http.json({ "sourceRefName": "refs/heads/copybara/sync", "targetRefName": "refs/heads/main", "title": "Sync from source: " + ctx.refs[0].ref, "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("Active PR already exists, skipping creation") return ctx.success() else: return ctx.error("Failed to create PR: " + str(response.status_code))
core.feedback( name = "create_azure_pr", origin = git.github_trigger( url = "https://github.com/org/source-repo", events = ["push"], ), destination = http.endpoint( hosts = [ http.host( host = "dev.azure.com", auth = http.basic_auth( username = credentials.static_value(""), password = credentials.static_secret("azure_pat", "AZURE_PAT"), ), ), ], ), actions = [core.action(impl = _create_azure_pr)],)CI/CD Integration
Section titled “CI/CD Integration”GitHub Actions (Syncing to Azure)
Section titled “GitHub Actions (Syncing to Azure)”name: Sync to Azure DevOps
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: AZURE_PAT: ${{ secrets.AZURE_PAT }} run: | # Configure Azure credentials echo "https://pat:$AZURE_PAT@dev.azure.com" >> ~/.git-credentials git config --global credential.helper store
# Run Copybara java -jar copybara.jar migrate copy.bara.sky sync --ignore-noop
- name: Create Azure PR if: success() env: AZURE_DEVOPS_EXT_PAT: ${{ secrets.AZURE_PAT }} run: | # Install Azure CLI extension az extension add --name azure-devops
# Configure defaults az devops configure --defaults \ organization=${{ vars.AZURE_ORG }} \ project=${{ vars.AZURE_PROJECT }}
# Check if PR already exists EXISTING_PR=$(az repos pr list \ --repository ${{ vars.AZURE_REPO }} \ --source-branch copybara/sync \ --status active \ --query "[0].pullRequestId" \ -o tsv)
if [ -z "$EXISTING_PR" ]; then az repos pr create \ --repository ${{ vars.AZURE_REPO }} \ --source-branch copybara/sync \ --target-branch main \ --title "Sync from GitHub" else echo "PR #$EXISTING_PR already exists" fiAzure Pipelines (Syncing from Azure)
Section titled “Azure Pipelines (Syncing from Azure)”trigger: branches: include: - main
pool: vmImage: "ubuntu-latest"
steps: - checkout: self fetchDepth: 0
- task: JavaToolInstaller@0 inputs: versionSpec: "21" jdkArchitectureOption: "x64" jdkSourceOption: "PreInstalled"
- script: | curl -fsSL -o copybara.jar \ https://github.com/google/copybara/releases/latest/download/copybara_deploy.jar displayName: "Download Copybara"
- script: | git config --global user.name "Azure Pipelines" git config --global user.email "pipelines@azure.com" echo "https://pat:$(DEST_PAT)@github.com" >> ~/.git-credentials git config --global credential.helper store displayName: "Configure Git"
- script: | java -jar copybara.jar migrate copy.bara.sky sync --ignore-noop displayName: "Run Copybara" env: DEST_PAT: $(GITHUB_PAT)API Reference
Section titled “API Reference”Azure DevOps REST API
Section titled “Azure DevOps REST API”Base URL: https://dev.azure.com/{organization}/{project}/_apis
| Endpoint | Method | Description |
|---|---|---|
/git/repositories/{repo}/pullrequests | POST | Create PR |
/git/repositories/{repo}/pullrequests | GET | List PRs |
/git/repositories/{repo}/pullrequests/{id} | GET | Get PR |
/git/repositories/{repo}/pullrequests/{id} | PATCH | Update PR |
/git/repositories/{repo}/pullrequests/{id}/completions | POST | Complete PR |
Authentication: Basic auth with empty username and PAT as password
API Version: Include ?api-version=7.0 on all requests
Documentation: Azure DevOps REST API
Common API Patterns
Section titled “Common API Patterns”List Active PRs
Section titled “List Active PRs”curl -H "Authorization: Basic $(echo -n ":$AZURE_PAT" | base64)" \ "https://dev.azure.com/$ORG/$PROJECT/_apis/git/repositories/$REPO/pullrequests?searchCriteria.status=active&api-version=7.0"Get PR by Source Branch
Section titled “Get PR by Source Branch”curl -H "Authorization: Basic $(echo -n ":$AZURE_PAT" | base64)" \ "https://dev.azure.com/$ORG/$PROJECT/_apis/git/repositories/$REPO/pullrequests?searchCriteria.sourceRefName=refs/heads/copybara/sync&api-version=7.0"Add Reviewers to PR
Section titled “Add Reviewers to PR”curl -X PATCH \ -H "Authorization: Basic $(echo -n ":$AZURE_PAT" | base64)" \ -H "Content-Type: application/json" \ -d '{ "reviewers": [ {"id": "user-guid-here"} ] }' \ "https://dev.azure.com/$ORG/$PROJECT/_apis/git/repositories/$REPO/pullrequests/$PR_ID?api-version=7.0"Troubleshooting
Section titled “Troubleshooting”Authentication Errors
Section titled “Authentication Errors”TF401019: The Git repository with name or identifier ... does not existSolutions:
- Verify PAT has
Code (Read & Write)scope - Check organization and project names
- Ensure repository name is correct (case-sensitive)
PR Creation Fails with 409
Section titled “PR Creation Fails with 409”{ "message": "An active pull request already exists" }Solutions:
- Query for existing PRs before creating
- Update the existing PR instead
- Use the wrapper script pattern that handles this case
Branch Not Found
Section titled “Branch Not Found”{ "message": "The source branch 'refs/heads/copybara/sync' does not exist" }Solutions:
- Verify Copybara pushed the branch successfully
- Check branch name format (should include
refs/heads/in API) - Ensure push completed before creating PR
TF400813: Resource Not Found
Section titled “TF400813: Resource Not Found”{ "$id": "1", "innerException": null, "message": "TF400813: ..." }Solutions:
- Verify API URL format matches Azure DevOps structure
- Include
api-versionparameter - Check project and repository exist
Next Steps
Section titled “Next Steps”- Overview - All approaches compared
- Bitbucket - Bitbucket Cloud and Server
- Gitea - Gitea, Forgejo, Codeberg
- HTTP module - Build custom API integrations