Skip to content

Frequently Asked Questions

Answers to frequently asked questions, sourced from the Copybara mailing list and GitHub issues.

Yes, by design. Copybara stores state in the destination repository itself, not in local files.

From Issue #196, a Copybara collaborator confirmed:

“Yes, it is stateless. We designed like that on purpose.”

A contributor clarified the mechanism:

“Copybara is stateless because it relies on the GitOrigin-RevId trailer existing in the destination repository in order to determine revision list that needs to be migrated.”

This means multiple users or CI jobs can run Copybara for the same config and get consistent results. The ~/copybara folder is only used for caching, not persistent state.

See also: State Management Guide

Can I use a custom state label instead of GitOrigin-RevId?

Section titled “Can I use a custom state label instead of GitOrigin-RevId?”

Yes. Use the custom_rev_id parameter in core.workflow.

From Custom label for copybara state:

There’s experimental_custom_rev_id setting in core.workflow which lets you customize the revision ID.

core.workflow(
name = "default",
# Use custom label instead of GitOrigin-RevId
custom_rev_id = "X-Backport-Id",
# ...
)

Yes, but it requires two separate workflows. Copybara requires one repository to be the source of truth.

From Issue #140:

A user asked about using Copybara for bidirectional sync between external (public) and internal (private) GitHub repos.

The pattern is:

  1. Push workflow: Internal → External (source of truth is internal)
  2. Pull workflow: Import external PRs back to internal

See also: Advanced Use Cases


How do I use environment variables in copy.bara.sky?

Section titled “How do I use environment variables in copy.bara.sky?”

You cannot directly reference environment variables like ${VAR} in the config file.

From Issue #125, the recommended approaches are:

1. Use Starlark macros to create parameterized workflows:

def make_workflow(name, destination_url):
return core.workflow(
name = name,
destination = git.destination(url = destination_url),
# ...
)
# Define multiple workflows
make_workflow("prod", "https://github.com/org/prod")
make_workflow("staging", "https://github.com/org/staging")

2. Use command-line flags to override values at runtime:

Terminal window
copybara migrate copy.bara.sky \
--git-destination-url "https://x-access-token:${TOKEN}@github.com/org/repo"

3. Generate config from template before running Copybara:

Terminal window
envsubst < copy.bara.sky.template > copy.bara.sky
copybara migrate copy.bara.sky

Can I split configuration across multiple files?

Section titled “Can I split configuration across multiple files?”

Yes! Use Starlark’s load() statement to import from other .bara.sky files.

From Issue #268, a Copybara collaborator confirmed:

“Yes, starlark has a load statement to include other files. You will want to write a function that returns a list of transforms.”

Example:

common.bara.sky
# Define reusable replacements
replacements = {
"old_name": "new_name",
"old_import": "new_import",
}
def common_transforms():
return [
core.replace(before = k, after = v)
for k, v in replacements.items()
]
copy.bara.sky
load("common", "common_transforms")
core.workflow(
name = "default",
transformations = common_transforms() + [
# workflow-specific transforms
],
# ...
)

This pattern is useful for:

  • Organizing transforms per directory/module
  • Sharing common replacements across workflows
  • Keeping the main config file clean

Why does my config file need to be named copy.bara.sky?

Section titled “Why does my config file need to be named copy.bara.sky?”

It’s a requirement, not just convention. The filename must be copy.bara.sky, but the path can vary.

From Issue #127, a Copybara maintainer clarified:

“This is expected. Copybara requires that the main config file is called ‘copy.bara.sky’. The path can change, but not the name.”

Valid examples:

Terminal window
# These work - different paths, same filename
copybara migrate /path/to/copy.bara.sky
COPYBARA_CONFIG=/configs/copy.bara.sky
# This does NOT work - different filename
COPYBARA_CONFIG=my-config.sky # Error!

No official image is published by Google. You can build from source or use community images.

From Issue #82:

It would be super helpful to have a copy of the latest master or release published as a docker image.

The issue remains open. Google has not published an official image.

Community alternatives:

  • Olivr/copybara-action - Builds nightly images
  • gcr.io/copybara-docker/copybara - Community-maintained image
  • Build your own from the Dockerfile in the repo

How do I pass command line arguments with Docker?

Section titled “How do I pass command line arguments with Docker?”

Use environment variables, as the entrypoint doesn’t pass arguments directly.

From Issue #158:

The Docker entrypoint does not pass command line arguments that are given to docker run.

Terminal window
docker run -e COPYBARA_WORKFLOW=my-workflow \
-e COPYBARA_SOURCEREF=main \
-v $(pwd):/usr/src/app \
copybara

Environment variables:

  • COPYBARA_CONFIG - Config file path (default: copy.bara.sky)
  • COPYBARA_WORKFLOW - Workflow name
  • COPYBARA_SOURCEREF - Source reference
  • COPYBARA_OPTIONS - Additional options
  • COPYBARA_SUBCOMMAND - Subcommand (migrate, validate, etc.)

Yes. Use the community action or Docker directly.

From Issue #123:

Is it possible to run Copybara in GitHub actions?

- uses: olivr/copybara-action@v1
with:
ssh_key: ${{ secrets.SSH_KEY }}

See also: GitHub Actions Guide


Challenge: GitHub doesn’t allow the same SSH key as a deploy key in two different repos.

From Issue #131:

Users wanted to give Copybara access to origin with read-only key and destination with read/write key.

Solutions:

  1. Use a machine user account with access to both repos
  2. Use GitHub App for authentication (Issue #264)
  3. Use HTTPS tokens instead of SSH:
    Terminal window
    echo "https://x-access-token:${TOKEN}@github.com" >> ~/.git-credentials

Mount your SSH credentials into the Docker container.

From Copybara for 2 private repos:

Terminal window
docker run -v ~/.ssh:/root/.ssh \
-v ~/.gitconfig:/root/.gitconfig \
-v $(pwd):/usr/src/app \
copybara migrate copy.bara.sky

See also: Authentication Guide


How do I test a Copybara workflow before running it?

Section titled “How do I test a Copybara workflow before running it?”

Use --dry-run and folder.destination.

From Recommended process for adding new workflows?:

How does one test a copybara workflow? Testing workflows is difficult.

Approaches:

  1. Dry run - Preview without making changes:

    Terminal window
    copybara migrate copy.bara.sky --dry-run
  2. Folder destination - Output to local folder for review:

    core.workflow(
    destination = folder.destination(),
    # ...
    )
  3. Validate command - Check config syntax:

    Terminal window
    copybara validate copy.bara.sky

From Understanding the workflow:

The workaround was to use folder destination so they could iterate and re-apply copybara multiple times, then push manually.

Use folder.destination() during development.

From Issue #43:

A user was wondering how to dry run Copybara to preview results.

# Development/testing workflow
core.workflow(
name = "test",
destination = folder.destination(),
# ... same config as production
)

Then inspect the output folder before switching to git.destination().


From Issue #43, there are two approaches:

Option 1: Glob addition + core.move (recommended for reversibility)

Include the overlay file via glob, then move it into place:

core.workflow(
# Include the overlay file explicitly
origin_files = glob(["**"], exclude = ["internal/**"]) + glob(["internal/README.md"]),
transformations = [
core.move("internal/README.md", "README.md"),
],
)

Option 2: core.copy with overwrite

Copy a file over an existing one:

transformations = [
core.copy(
before = "internal/README.md",
after = "README.md",
overwrite = True,
),
]

Where can I find examples of custom transformations?

Section titled “Where can I find examples of custom transformations?”

From Issue #254:

A user asked for examples of more complex transformations beyond simple copies and moves.

Resources:


When should I use SQUASH vs ITERATIVE mode?

Section titled “When should I use SQUASH vs ITERATIVE mode?”

From Issue #265:

SQUASH mode:

  • Combines all commits into one
  • Better for detecting empty/duplicate changes
  • Recommended when you don’t need individual commit history

ITERATIVE mode:

  • Preserves individual commits
  • Can cause issues with bidirectional sync (commits may duplicate)
  • Best when combined with CHANGE_REQUEST for the reverse direction

A maintainer explained:

“Copybara enforces a single source of truth for a set of files.”

Recommended pattern:

# Source of truth → mirror (ITERATIVE)
core.workflow(
name = "push",
mode = "ITERATIVE",
# ...
)
# External PRs → source of truth (CHANGE_REQUEST)
core.workflow(
name = "pull",
mode = "CHANGE_REQUEST",
# ...
)

No. Copybara produces linear history by design.

From Issue #99, a maintainer confirmed:

“Copybara moves commits as linear history.”

Even with first_parent = False, merge commits are treated as regular commits. If you need to preserve merge structure, consider:

  • Using integrates for external changes
  • Using git.github_pr_destination for PR-based workflows
  • Adding metadata.expose_label("COPYBARA_INTEGRATE_REVIEW")

What’s the difference between origin_files and destination_files?

Section titled “What’s the difference between origin_files and destination_files?”

From Issue #56 and Issue #75:

origin_files - Controls which files are read from source:

# Only sync src/ and docs/, exclude secrets
origin_files = glob(["src/**", "docs/**"], exclude = ["**/*.secret"])

destination_files - Controls which files can be modified in destination:

# Only touch files in vendor/lib/, leave everything else alone
destination_files = glob(["vendor/lib/**"])

Why are excluded files still being copied?

Section titled “Why are excluded files still being copied?”

From Issue #56:

Problem: Files added to exclude array still appear in destination.

Solution: Make sure exclusions are in origin_files, not destination_files:

# CORRECT - exclude in origin_files
origin_files = glob(["**"], exclude = ["secrets/**", "*.key"])
# WRONG - this only prevents writing/deleting, not reading
destination_files = glob(["**"], exclude = ["secrets/**"])

Why does Copybara say “No changes to migrate”?

Section titled “Why does Copybara say “No changes to migrate”?”

The destination already has all changes from origin.

Copybara reads GitOrigin-RevId from the last destination commit and only syncs newer changes.

Solutions:

  1. Use --ignore-noop to not fail on no changes
  2. Use --force to re-sync anyway
  3. Use --last-rev to start from a specific commit

See also: Common Issues

What does “No new changes to import for resolved ref” mean?

Section titled “What does “No new changes to import for resolved ref” mean?”

From Issue #27:

This warning appears when you specify a --last-rev that’s already the final commit in your source repository.

Solution for importing full history:

Terminal window
# First run - point to earliest commit to import
copybara migrate copy.bara.sky --last-rev <first-commit-sha>
# Subsequent runs - no flag needed, Copybara auto-detects
copybara migrate copy.bara.sky

A contributor noted there’s no “zero” SHA-1 reference in Git for importing from the very beginning, so you must specify the first commit you want.

From Copybara is stuck on the initial run:

Common causes:

  1. Large repository - Initial clone takes time
  2. Network issues - Slow connection to Git server
  3. Many commits - Use --last-rev to limit history
Terminal window
# Start from recent history only
copybara migrate copy.bara.sky --last-rev HEAD~100

From Issue #84 and Issue #216:

Submodule not registered in destination / Cannot find object when running with submodules.

Copybara has limited submodule support. Consider:

  1. Using git.origin() with submodules = "NO" (default)
  2. Flattening submodules before sync
  3. Syncing submodule content separately

Does Copybara support GitLab/Bitbucket natively?

Section titled “Does Copybara support GitLab/Bitbucket natively?”

Git operations work, but no native API integration (unlike GitHub).

From Issue #153:

GitLab support is requested.

Workaround: Use git.origin() and git.destination() with HTTPS/SSH URLs.

See also: Unsupported Platforms

Not recommended, but possible with custom scripts.

From Issue #136:

A user wanted to protect the branch in the destination repo.

Copybara is designed for incremental sync, not force pushes. If you need to rewrite history, consider resetting the destination branch manually first.