Skip to content

Import Repositories to Harness Code

Mohit Suman edited this page Aug 26, 2026 · 2 revisions

Migrate Code Repositories to Harness Code

This guide explains how developers and customers use the Harness CLI to migrate git repositories: including pull requests, comments, labels, webhooks, and branch rules, from any supported SCM into Harness Code.


Table of contents

  1. What gets migrated
  2. Supported source providers
  3. Prerequisites
  4. Install the CLI and migrate plugin
  5. Migration overview
  6. Step 1 — Authenticate to Harness (import only)
  7. Step 2 — Export from your SCM
  8. Step 3 — Map user emails (optional)
  9. Step 4 — Import into Harness Code
  10. Step 5 — Verify the migration
  11. Provider-specific reference
  12. Advanced scenarios
  13. Command quick reference
  14. Troubleshooting

What gets migrated

A full migration can include:

Entity Export Import
Git repository (all branches and tags)
Git LFS objects ✓ (requires git-lfs)
Repository visibility (public/private)
Pull requests / merge requests
PR comments and review comments
PR reviewers
Labels
Webhooks
Branch protection / branch rules

Use --no-* flags on export or import to skip specific entity types (see Command quick reference).


Supported source providers

SCM Export command --from identifies
GitHub (cloud or Enterprise) migrate github_organization:scm_bundle GitHub organization
GitLab (cloud or self-managed) migrate gitlab_group:scm_bundle GitLab group
Bitbucket Cloud migrate bitbucket_workspace:scm_bundle Bitbucket workspace
Bitbucket Server (Stash) migrate stash_project:scm_bundle Bitbucket Server project

All exports write the same scm_bundle format. A single import command consumes bundles from any provider:

harness migrate scm_bundle:repository --from ./harness

Prerequisites

Tools

Tool Required for Notes
Harness CLI (harness) All steps See Install the CLI and migrate plugin
migrate plugin Export and import Installed separately via harness install plugin migrate
git Export and import Must be on PATH
git-lfs Repos with LFS objects Must be on PATH; skip with --no-lfs if not needed

Source SCM tokens

Create a personal access token on your source SCM with read access to the org/group/workspace/project you are migrating. Minimum scopes vary by provider — see Token permissions.

You can pass tokens via command flags (--github-token, --gitlab-token, etc.) or environment variables where supported.

Harness token (import only)

The export phase does not require a Harness login. The import phase requires a Harness API token (PAT or SAT) with permission to create repositories in the target org/project.

Export runs entirely against your source SCM. Import creates resources under the account/org/project in your active CLI profile.


Install the CLI and migrate plugin

Install or upgrade the CLI

harness install cli

For scripting/CI, see docs/manual-install.md.

Install the migrate plugin

harness install plugin migrate

Check availability without installing:

harness install plugin migrate --check

Confirm both are loaded:

harness list module

You should see code (builtin) and migrate (plugin, installed).

Discover migrate commands

harness get module migrate
harness migrate --help
harness get noun scm_bundle

Migration overview

Migration is deliberately two phases separated by a bundle on disk:

┌─────────────────┐     export      ┌──────────────┐     import      ┌─────────────────┐
│  Source SCM     │ ──────────────► │  scm_bundle  │ ──────────────► │  Harness Code   │
│  (GitHub, etc.) │  (no Harness    │  ./harness/  │  (Harness auth  │  repositories   │
│                 │   login needed) │  harness.zip │   required)     │  + PR metadata  │
└─────────────────┘                 └──────────────┘                 └─────────────────┘
                                           │
                                           ▼
                              update scm_bundle:users  (optional)
                              rewrite emails in bundle

Why two steps?

  • Org-scale exports can run for hours; checkpoints and --resume let you recover from interruptions.
  • The bundle can be inspected, backed up, or edited (e.g. user email mapping) before anything is created in Harness.
  • Export can run on a machine with SCM access but no Harness credentials (e.g. inside your corporate network or on the SCM server itself).

Step 1 — Authenticate to Harness (import only)

Skip this step if you are only exporting. Come back here before running the import.

Interactive login

harness auth login

The wizard prompts for API URL, token, org, and project.

Non-interactive login (CI / automation)

harness auth login \
  --api-url https://app.harness.io \
  --api-token "$HARNESS_API_KEY" \
  --org <org_id> \
  --project <project_id>

For self-hosted Harness, set --api-url to your instance URL.

Set or change default scope

Repositories are created under the profile's org/project:

harness auth setscope --org <org_id> --project <project_id>

Or override per command:

harness migrate scm_bundle:repository --from ./harness --org <org_id> --project <project_id>

Verify auth

harness auth status
harness list repository

Step 2 — Export from your SCM

Pick the command that matches your source provider. Each command:

  • Reads repositories and metadata from the live SCM API
  • Writes a local bundle to ./harness by default (override with --to)
  • Produces harness.zip inside the output folder
  • Supports --resume to continue an interrupted export
  • Supports --repo to export a single repository instead of the whole org/group/project

GitHub

harness migrate github_organization:scm_bundle \
  --from <github_org> \
  --github-token <token> \
  --github-user <username> \
  --to ./harness

GitHub Enterprise:

harness migrate github_organization:scm_bundle \
  --from <github_org> \
  --github-host https://github.example.com \
  --github-token <token> \
  --github-user <username>

Single repository:

harness migrate github_organization:scm_bundle \
  --from <github_org> \
  --repo <repo_name> \
  --github-token <token> \
  --github-user <username>

GitLab

harness migrate gitlab_group:scm_bundle \
  --from <group_path> \
  --gitlab-token <token> \
  --gitlab-user <username> \
  --to ./harness

Self-managed GitLab:

harness migrate gitlab_group:scm_bundle \
  --from <group_path> \
  --gitlab-host https://gitlab.example.com \
  --gitlab-token <token> \
  --gitlab-user <username>

Include nested subgroups:

harness migrate gitlab_group:scm_bundle \
  --from <group_path> \
  --include-subgroups \
  --gitlab-token <token> \
  --gitlab-user <username>

Single project:

harness migrate gitlab_group:scm_bundle \
  --from <group_path> \
  --repo <project_name> \
  --gitlab-token <token> \
  --gitlab-user <username>

Bitbucket Cloud

harness migrate bitbucket_workspace:scm_bundle \
  --from <workspace> \
  --bitbucket-token <token> \
  --to ./harness

Single repository:

harness migrate bitbucket_workspace:scm_bundle \
  --from <workspace> \
  --repo <repo_slug> \
  --bitbucket-token <token>

Bitbucket Server (Stash)

harness migrate stash_project:scm_bundle \
  --from <project_key> \
  --stash-host https://bitbucket.example.com \
  --stash-token <token> \
  --stash-user <username> \
  --to ./harness

Single repository:

harness migrate stash_project:scm_bundle \
  --from <project_key> \
  --repo <repo_slug> \
  --stash-host https://bitbucket.example.com \
  --stash-token <token> \
  --stash-user <username>

Resume an interrupted export

If export stops partway through, re-run the same command with --resume:

harness migrate github_organization:scm_bundle \
  --from <github_org> \
  --github-token <token> \
  --github-user <username> \
  --to ./harness \
  --resume

Export output

After a successful export, expect:

./harness/
  harness.zip    # bundle ready for import
  ...            # checkpoint and intermediate files

No Harness resources are created during export.


Step 3 — Map user emails (optional)

Harness matches PR authors, commenters, reviewers, and branch-rule members by email. If a source SCM email does not match any Harness user, the import stops.

Fix this before import by rewriting emails in the bundle:

Create a mapping file

users.json:

{
  "alice@old-company.com": "alice@new-company.com",
  "bob@github-noreply.com": "bob@harness.example.com"
}

Apply the mapping

harness update scm_bundle:users ./harness --user-mapping users.json

This rewrites the bundle in place (the original is replaced only after success).

Alternatively, skip strict user matching at import time (see Import with unmapped users).


Step 4 — Import into Harness Code

With Harness auth configured and the bundle ready:

harness migrate scm_bundle:repository --from ./harness

You can pass the folder (./harness) or the zip directly (./harness/harness.zip).

Import a single repository from the bundle

harness migrate scm_bundle:repository \
  --from ./harness \
  --repo <org_or_group>/<repo_name>

Large repositories

Raise the temporary git push size limit (default 100 MB):

harness migrate scm_bundle:repository \
  --from ./harness \
  --file-size-limit 500000000

API payload size (413 errors)

Lower the PR batch size:

harness migrate scm_bundle:repository \
  --from ./harness \
  --batch-size 50

Import with unmapped users

To continue when some emails have no Harness match (PRs/comments/rules are attributed to your profile's account):

harness migrate scm_bundle:repository \
  --from ./harness \
  --skip-users

Use a service account profile when using --skip-users, so unmapped history is not credited to a personal account.

Commit authors always come from git objects in the bundle, not from the importing identity.

Long-running imports

Org-scale imports can take hours. Disable the timeout:

harness migrate scm_bundle:repository --from ./harness --timeout 0

Step 5 — Verify the migration

Use the built-in code module to confirm repositories and metadata landed correctly.

List migrated repositories

harness list repository
harness list repository --org <org_id> --project <project_id>

Inspect a repository

harness get repository <repo_id>

Check branches, tags, and commits

harness list branch <repo_id>
harness list tag <repo_id>
harness list commit <repo_id> --branch main

Check pull requests

harness list pr <repo_id>
harness list pr <repo_id> --state open
harness get pr <repo_id>/<pr_number>

Check PR activity, comments, and checks

harness list pr_activity <repo_id>/<pr_number>
harness list pr_comment <repo_id>/<pr_number>
harness list pr_check <repo_id>/<pr_number>

Check labels

harness list repo_label <repo_id>
harness list pr_label <repo_id>/<pr_number>

Provider-specific reference

Token permissions by provider

Provider Token type Minimum permissions / scopes
GitHub Classic PAT repo, read:org
GitHub Enterprise Same as above Admin/write on repos recommended for full metadata export
GitLab PAT read_api, read_repository
Bitbucket Cloud App password / PAT Repository Read
Bitbucket Server HTTP access token Repository Read on target project
Harness (import) PAT or SAT Permission to create Code repositories in target org/project

For GitHub, admin/write access on repositories helps export webhooks, branch rules, and full PR metadata.

Host URL flags

Provider Flag Example
GitHub Enterprise --github-host https://github.example.com
Self-managed GitLab --gitlab-host https://gitlab.example.com
Bitbucket Server --stash-host https://bitbucket.example.com

Bitbucket Cloud uses the public API by default; --bitbucket-host is available for non-standard endpoints.


Advanced scenarios

Incremental PR import (--no-git)

When a repository already exists in Harness Code and you only need additional PR metadata:

harness migrate scm_bundle:repository \
  --from ./harness \
  --no-git

This skips git push operations and imports only pull requests, comments, and related metadata. The CLI calculates PR number offsets to avoid conflicts with existing PRs.

Requirements:

  • Target repository must already exist in Harness Code
  • You must have write permission on the target repository

Export without PR metadata (git only)

Useful for a first pass or when PR export is slow:

harness migrate github_organization:scm_bundle \
  --from <org> \
  --no-pr \
  --github-token <token> \
  --github-user <username>

Then run a second export with --no-git on import, or export PRs separately.

Selective export/import flags

Flag Effect
--no-pr Skip pull requests
--no-pr-metadata Skip PR comments and reviewers (keep PR shells)
--no-comment Skip PR comments
--no-label Skip labels
--no-webhook Skip webhooks
--no-rule Skip branch protection rules
--no-lfs Skip LFS objects

These flags work on both export (<provider>:scm_bundle) and import (scm_bundle:repository) commands.

Run export near the SCM server

For repos behind a firewall, run export on a host that has direct SCM access (your laptop on VPN, a jump box, or the SCM server itself). Copy the ./harness folder to a machine with Harness access for import:

# On SCM-accessible host — export only, no Harness credentials needed
harness migrate github_organization:scm_bundle \
  --from <org> \
  --github-token <token> \
  --github-user <username> \
  --to /data/migration-bundle

# Copy /data/migration-bundle to import host, then:
harness migrate scm_bundle:repository --from /data/migration-bundle

Automate in CI/CD

Typical pipeline stages:

  1. Export job — SCM credentials only; artifact the ./harness folder
  2. Optional transform jobharness update scm_bundle:users
  3. Import job — Harness credentials; harness migrate scm_bundle:repository

Use non-interactive auth:

export HARNESS_API_KEY=<token>
export HARNESS_ACCOUNT=<account_id>
export HARNESS_ORG=<org_id>
export HARNESS_PROJECT=<project_id>

harness migrate scm_bundle:repository --from ./harness --timeout 0

See docs/auth.md for env-var mode details.


Command quick reference

Export commands (source → local bundle)

# GitHub
harness migrate github_organization:scm_bundle --from <org> [--to <folder>] [--repo <name>] [--resume] \
  [--github-token <token>] [--github-user <user>] [--github-host <url>]

# GitLab
harness migrate gitlab_group:scm_bundle --from <group> [--to <folder>] [--repo <name>] [--resume] [--include-subgroups] \
  [--gitlab-token <token>] [--gitlab-user <user>] [--gitlab-host <url>]

# Bitbucket Cloud
harness migrate bitbucket_workspace:scm_bundle --from <workspace> [--to <folder>] [--repo <name>] [--resume] \
  [--bitbucket-token <token>] [--bitbucket-host <url>]

# Bitbucket Server
harness migrate stash_project:scm_bundle --from <project> [--to <folder>] [--repo <name>] [--resume] \
  [--stash-host <url>] [--stash-token <token>] [--stash-user <user>]

Transform command (bundle editing)

harness update scm_bundle:users <folder-or-zip> --user-mapping users.json

Import command (bundle → Harness Code)

harness migrate scm_bundle:repository --from <folder-or-zip> [--repo <org/repo>] \
  [--skip-users] [--no-git] [--batch-size <n>] [--file-size-limit <bytes>] [--timeout 0]

Post-migration verification (code module)

harness list repository
harness get repository <repo_id>
harness list pr <repo_id>
harness list branch <repo_id>
harness list repo_label <repo_id>

Setup commands

harness install cli
harness install plugin migrate
harness auth login
harness auth setscope --org <org> --project <project>
harness get module migrate

Troubleshooting

unknown command "migrate"

The migrate plugin is not installed:

harness install plugin migrate
harness list module    # migrate should show Installed: yes

Import fails on unknown user email

Option A — map emails before import:

harness update scm_bundle:users ./harness --user-mapping users.json
harness migrate scm_bundle:repository --from ./harness

Option B — skip strict matching (attributes unmapped users to your profile):

harness migrate scm_bundle:repository --from ./harness --skip-users

Export interrupted mid-run

Re-run with --resume and the same --to folder:

harness migrate github_organization:scm_bundle --from <org> --to ./harness --resume ...

HTTP 413 during import

Reduce batch size:

harness migrate scm_bundle:repository --from ./harness --batch-size 25

Large file push failures

Increase file size limit:

harness migrate scm_bundle:repository --from ./harness --file-size-limit 1000000000

LFS objects missing

Ensure git-lfs is installed on both export and import hosts. Do not pass --no-lfs unless you intentionally skip LFS.

Wrong org/project after import

Import scope comes from the active profile or --org/--project flags. Set scope before import:

harness auth setscope --org <org> --project <project>
harness migrate scm_bundle:repository --from ./harness

Discover command flags at any time

harness migrate <provider>:scm_bundle --help
harness migrate scm_bundle:repository --help
harness update scm_bundle:users --help

Related documentation

Clone this wiki locally