Skip to content

Latest commit

 

History

History
522 lines (426 loc) · 34.8 KB

File metadata and controls

522 lines (426 loc) · 34.8 KB

Using transfer — Transfer One Project

transfer is the single-command, project-scoped path. It chains the four phases of a migration — extract → structure → mappings → migrate — into one call, then writes a PDF summary on completion. Use it when you have one project (or a small, well-known set of projects) to move across.

Unlike a full migrate, transfer only touches the specified project and the entities it actually uses — its quality gate, its quality profiles, its permissions and project settings, and its complete issue and Security Hotspot history (including externally imported issues). Instance-wide entities such as portfolios, global settings, permission templates, and default gate/profile selection are not modified. See What gets migrated below.

If you need fine-grained control, want to review the intermediate files between phases, or are migrating many projects across multiple SonarQube Server instances, see Using migrate instead.


When to use it

  • Migrating a single project from SonarQube Server to SonarQube Cloud.
  • Quick one-off moves where you don't need to review intermediate files.
  • Smoke-testing the tool against a known project before a larger migration.

If any of these sound like you, jump to MIGRATE.md instead:

  • Multiple SonarQube Server instances.
  • You want to inspect or edit the mapping CSVs before pushing.
  • You want to run the phases at different times (e.g., extract on a Friday, migrate on a Monday).
  • You want to resume a partial migration after a failure.

What it does

Behind the scenes, transfer runs the same four phases as the manual workflow, in order:

  1. Extract — connects to SonarQube Server and pulls the project's configuration and its full issue/hotspot project data (project data is always included for transfer).
  2. Structure — assembles the extracted data into the project + org structure.
  3. Mappings — generates the per-entity mapping CSVs (gates, profiles, groups, templates, portfolios).
  4. Migrate — applies the project-scoped subset to SonarQube Cloud: it runs only the tasks needed for the project, its quality gate and profiles, its permissions, and its issue/hotspot history. Their dependencies are resolved automatically; global/instance-wide tasks are skipped.

On completion, a migration summary is written into the export directory as both migration_summary.pdf and migration_summary.md, alongside the run instrumentation files (run_meta.json, run_events.jsonl). These are written even when the run fails, so the summary can explain the failure.


What gets migrated

transfer migrates a project-scoped slice, not the whole instance.

Included:

  • The specified project (created in the target organization).
  • The quality gate the project uses, with its conditions.
  • The quality profiles the project uses, with their rules restored (and any parent relationships).
  • The project's permissions (group permissions), settings, tags, links, webhooks, and new code period.
  • The project's complete issue history — both native SonarQube issues and externally imported issues (from third-party analyzers) — replayed via project-data import, with triage state (status, resolution, assignee, comments, tags) synced afterward.
  • The project's Security Hotspots, with their review status and comments synced.
  • The project's DevOps platform (ALM) binding — the project is bound on SonarQube Cloud to the repository it was bound to on SonarQube Server (issue #122). Only the project-level binding is replicated: the organization's own DevOps platform binding needs secrets that cannot be migrated and is read-only input here. The binding is attempted only when the source project is bound and the target organization is itself bound to the same platform; otherwise the project is reported as a partial migration explaining why. See What gets migrated → DevOps platform bindings.

Not modified (use the full migrate command for these):

  • Portfolios.
  • Global settings, global webhooks, and the global new code period.
  • Permission templates and default-template assignment.
  • Organization-level and profile-level group permissions.
  • Default quality gate / default quality profile selection.
  • Rule tag and rule description updates.

Note on prerequisites. A few global entities are created on the target only because the project depends on them — for example, the groups referenced by the project's group permissions, and the migration user/permissions used to perform the migration. These are created as needed so the project's own configuration resolves correctly.

Note on issue counts. The target issue count is normally lower than the SonarQube Server total because issues that are CLOSED or resolved as FIXED have no SonarQube Cloud counterpart and are intentionally skipped (the scanner report only recreates active findings). Open issues plus triaged ones (won't-fix / false-positive / accepted) and all externally-imported issues are migrated. Security Hotspots transfer in full, but they arrive as issues (see above) — so they are counted inside the target issue total, and SonarQube Cloud's Security Hotspots view will be empty by design. Comparing a source hotspot count against a target hotspot count therefore always reads as total loss even when every hotspot migrated correctly; filter the target project by the sqs-hotspot tag instead.

Non-main branches. Project-data import now migrates the project's non-main branches too — each is created on SonarQube Cloud as a long-lived branch with its full issue history. Before submitting a non-main branch's report, the tool performs SonarQube Cloud's "Create analysis" handshake (POST {api-host}/analysis/analyses) to register the branch and obtain an analysis id, which it embeds in the report so the Compute Engine binds the issues to the branch. All migrated branches are registered as long-lived so SonarQube Cloud's automatic pruning of short-lived branches (after ~30 days) never discards migrated history. A non-main branch is skipped only when the source server no longer has its source code (e.g. purged by housekeeping for an inactive branch) — re-analyze that branch on the source first to restore it.

Branch cap. At most 10 long-lived branches per project are migrated — a safeguard against projects with heavy branch sprawl (little branch housekeeping, or a branch selection that casts too wide a net) taking too long to migrate or putting too much pressure on SonarQube Cloud's API. When a project has more than 10 eligible branches, the main branch and any branch literally named master or develop are always kept, then branches matching [Rr]elease.* (most recently analyzed first), then the remaining branches (also most recently analyzed first) fill any leftover slots. Branches dropped by the cap are named in the migration report's Details column for that project. This limit does not apply to extract, and is not configurable — it is a fixed internal safeguard.

DevOps platform (ALM) bindings

transfer replicates the project's DevOps platform binding so the migrated project is linked to the same repository on SonarQube Cloud. The identifier carried over per platform is:

Platform Identifier migrated
GitHub Repository name (owner/repo)
GitLab Project id
Azure DevOps Project name + repository name
Bitbucket Cloud Repository slug

Preconditions. Two must both hold, and both are checked before any write:

  1. The source project is bound on SonarQube Server (GET /api/alm_settings/get_binding). An unbound project is simply left unbound on the target — nothing is reported.
  2. The target organization is bound to the same DevOps platform (GET /api/alm_integration/show_bound_organization). SonarQube Cloud can only bind a project to a repository of the DevOps organization its own organization is bound to.

When the source project was bound but the target organization is not bound to that platform, the project's migration outcome becomes Partial Migration and the report's Details column reads "project binding was not possible because the org itself is not bound". The same happens, with a different sentence, when the organization is bound but the repository does not exist in the bound DevOps organization.

Both preconditions are best-effort and never fail the migration (issue #505). Reading them only enables this optional extra, so any failure degrades to "no binding" and the run continues:

What the target answers Recorded as Report Details
show_bound_organization → HTTP 500 (SonarQube Cloud's normal answer for an org with no DevOps binding), 404 (no such org), 400/403 (token cannot administer it) unbound "...because the org itself is not bound"
show_bound_organization → any other failure (transport error, 502/503, ...) binding unknown "...because the target organization's DevOps platform binding could not be read" + the API error
list_repositories → HTTP 400 "This organization is not bound to an ALM application" / 403 / 404 no repositories the unbound-org sentence above (reported from the org binding)
list_repositories → any other failure repositories unknown "...because the repositories of the bound DevOps organization could not be listed" + the API error

Only a cancelled or timed-out run still aborts these tasks. The distinction between "unbound" and "unknown" is deliberate: before #505 an unbound org's HTTP 500 aborted the entire migrate run with phase 2: task getOrgBinding: ..., and reporting an unread binding as "not bound" would state something the tool never observed.

On-premise DevOps platforms are never migrated. SonarQube Cloud integrates only with the cloud platforms — GitHub.com, GitLab.com, Azure DevOps Services and Bitbucket Cloud — so a source project bound to GitHub Enterprise Server, self-managed GitLab or Bitbucket Server/Data Center has no target equivalent. Cloud vs on-premise is decided from the source ALM setting's url (its API endpoint: api.github.com, gitlab.com, dev.azure.com, visualstudio.com for Azure DevOps Services accounts predating the rename, bitbucket.org). Such a project is reported as Partial Migration with "project binding was not possible because the source project is bound to an on-premise DevOps platform, which SonarQube Cloud cannot integrate with" — before #505 the binding was dropped silently and the project was reported as fully migrated.

A project that is not bound at all on the source is still left unbound on the target with nothing reported, which is the #122 behaviour.


Quick start

With a config file

sonar-migration-tool transfer -c config.json

config.json uses the same unified shape as extract and migrate — one top-level block of shared defaults plus source and target sub-objects. See ADVANCED-CONFIG.md for the full reference.

Minimal form:

{
  "source": {
    "url": "https://sonarqube.example.com",
    "token": "sqp_xxx"
  },
  "target": {
    "token": "squ_xxx",
    "default_organization": "my-org"
  }
}

Full form:

{
  "concurrency": 25,
  "timeout": 60,
  "export_directory": "./migration-files",
  "project_key_regexp": "my-project",
  "source": {
    "url": "https://sonarqube.example.com",
    "token": "sqp_xxx",
    "pem_file_path": "/path/to/cert.pem",
    "key_file_path": "/path/to/cert.key",
    "cert_password": "optional"
  },
  "target": {
    "url": "https://sonarcloud.io/",
    "token": "squ_xxx",
    "default_organization": "my-org",
    "enterprise_key": "my-enterprise"
  }
}

With CLI flags

sonar-migration-tool transfer \
  --source_url https://sonarqube.example.com \
  --source_token sqp_xxx \
  --project_key_regexp my-project \
  --target_token squ_xxx \
  --default_organization my-org

--project_key_regexp is always compiled as a full-match regex, implicitly anchored with ^ and $ (issue #529). A plain key like my-project matches only itself, so single-project usage is unaffected. A pattern transfers every source project whose key fully matches it in one run:

# Transfers every project whose key starts with "BANKING_" — not a key
# that merely contains "BANKING_" somewhere in the middle.
sonar-migration-tool transfer \
  --source_url https://sonarqube.example.com \
  --source_token sqp_xxx \
  --project_key_regexp "BANKING_.+" \
  --target_token squ_xxx \
  --default_organization my-org

--project_key_regexp (or its deprecated alias --project_key, or project_key_regexp/project_key in the config file) is required — transfer is project-scoped by design. Use the step-by-step migrate workflow (see MIGRATE.md) to transfer every project in one run.

Note: transfer runs structure itself, which rewrites projects.csv, so the per-project organization override described in Mapping unbound SonarQube Server projects cannot be used with transfer. Use the step-by-step migrate workflow when you need it. (Issue #612.)


Flags

Flag Config key Description
-c, --config — Path to a JSON configuration file (see ADVANCED-CONFIG.md)
--source_url source.url SonarQube Server URL
--source_token source.token SonarQube Server token
--project_key_regexp project_key_regexp Project key (or regexp) to transfer (required; transfer is project-scoped by design). Always compiled as a full-match regex, implicitly anchored with ^ and $ — a plain key matches only itself; a pattern like BANKING_.+ transfers every project whose key starts with BANKING_. The deprecated --project_key / project_key still work but log a warning; --project_key_regexp takes precedence if both are set.
--target_url target.url SonarQube Cloud URL (default: https://sonarcloud.io/)
--target_token target.token SonarQube Cloud token
--default_organization target.default_organization SonarQube Cloud organization key
--enterprise_key target.enterprise_key SonarQube Cloud enterprise key (defaults to --default_organization)
--export_dir export_directory Working directory for intermediate files (default: ./migration-files/)
--concurrency concurrency Max concurrent HTTP requests (default: 25)
--timeout timeout HTTP request timeout in seconds
--pem_file_path source.pem_file_path Client mTLS PEM file for the source server
--key_file_path source.key_file_path Client mTLS key file for the source server
--cert_password source.cert_password Password for the source server mTLS client certificate
--insecure source.insecure Skip TLS certificate verification for the source SonarQube Server connection. For a trusted internal server whose certificate is self-signed or not signed by a trusted CA; leaves the connection open to man-in-the-middle interception. Defaults to off. Issue #586.
--skip_project_data_migration top-level skip_project_data_migration Skip the project-data migration (importProjectData + per-issue / per-hotspot sync). Defaults to off — project data is migrated by default. Issue #303.
--exclude_branches target.exclude_branches Glob patterns for non-main branches to skip during project data import. Repeatable. Main branch is never excluded.
--branch_regexp branch_regexp Regexp pattern of branch names to extract/migrate, always compiled as a full-match regex, implicitly anchored with ^ and $ — a plain name matches only itself. Applies to both phases of the transfer. Omit to process every branch. The main branch is always included regardless of match. Issue #582.
--unsupported_languages top-level or target.unsupported_languages How to handle files whose language has no quality profile on the target — typically a language from a 3rd-party SonarQube Server plugin. exclude (default) drops those files from the analysis report so the rest of the project still migrates; skip does not migrate the project's issues/branches at all; warn submits the report unchanged. Issue #474.
--migrate_history top-level migrate_history PoC. Also migrate a bounded set of historical analysis snapshots (date + project-level measures only) per migrated branch (every branch, not just main — #625), backdated on SonarQube Cloud. Defaults to off — no change to existing behavior unless set. Issue #554.
--history_max_points top-level history_max_points Max historical snapshots migrated per branch (not per project — each migrated branch gets its own cap) when --migrate_history is set (default: 0, no cap — every analysis is a candidate).
--history_min_interval_days top-level history_min_interval_days Minimum spacing, in days, enforced between two migrated historical snapshots of the same branch when --migrate_history is set (default: 0, no spacing rule).
--branch_analyzed_after source.branch_analyzed_after + target.branch_analyzed_after Only select branches analyzed on or after this YYYY-MM-DD date. The project's main branch is always selected regardless. Omit to select all branches (default). Issue #583.

CLI flags override values from the config file when both are provided.

--branch_analyzed_after is applied to both the extract and migrate phases at once when passed on the CLI — unlike --exclude_branches (migrate-only), a transfer talks to both sides in one invocation. Giving the two phases genuinely different cutoffs (per one of the worked examples in ADVANCED-CONFIG.md) requires the config file's source.branch_analyzed_after / target.branch_analyzed_after; there is deliberately no fallback from one side to the other, so setting only one leaves the other phase unfiltered.

Unsupported programming languages (--unsupported_languages)

SonarQube Server can analyze languages SonarQube Cloud cannot. A language contributed by a 3rd-party (non-SonarSource) plugin has no analyzer on the Cloud side, and therefore no quality profile.

The analysis report transfer fabricates stamps every file with the language the source server reported for it, while the report's metadata can only name quality profiles that exist on the target. When a file's language has no target profile, the SonarQube Cloud Compute Engine rejects the entire report:

Report contains a file with language 'lua' but no matching quality profile

The project, its permissions and its quality gate are created before the report is submitted, so the result is a project that looks migrated but has no issues and no branches.

transfer detects this before submitting and prints the affected languages, the file count and an example path. Choose the handling with --unsupported_languages:

Mode Behaviour
exclude (default) Drops the unsupported-language files from the report. Everything else — the other files, their issues, measures and all branches — migrates. The project is reported as a Partial Migration with the languages and file count listed.
skip Submits no report for the project. Its settings, permissions and quality gate still migrate; its issues and branches do not. Reported as skipped, with the reason.
warn Submits the report unchanged (pre-#474 behaviour). The Compute Engine is expected to reject it; the rejection is reported as such rather than as a generic API error.
# Migrate everything except the unsupported-language files (default)
sonar-migration-tool transfer -c config.json --project_key_regexp my-project

# Do not transfer this project's issues/branches at all
sonar-migration-tool transfer -c config.json --project_key_regexp my-project \
  --unsupported_languages skip

A failure to read the target organization's quality profiles disables the detection entirely rather than treating every language as unsupported, so a transient API error can never drop a project's files.

Current-snapshot backdating

Since #557, every branch's regular current-snapshot import stamps its analysis with the source branch's real last-analysis date — read from api/project_branches/list's analysisDate field — instead of the migration run's own wall-clock time. This is unconditional: it applies to every branch whether or not --migrate_history is set, unlike the historical points described below. Only the analysis's own stamped date changes; other fallback dates used elsewhere in the import are untouched.

Project history migration (--migrate_history) — PoC

This is a proof-of-concept. By default, transfer (and migrate) submit a single scanner report per branch. Since #557, that report is backdated to the source branch's real last-analysis date instead of "now" (see Current-snapshot backdating above) — but without --migrate_history, it is still only one point: the target's analysis history starts there, even if the source project has years of prior analyses. Issue #554 asks for a way to carry some of that history over.

--migrate_history opts into replaying a bounded set of a project's historical analyses — on every migrated branch, not just main (#625) — as separate, backdated entries on the target, submitted before each branch's regular current-snapshot import so each lands as its own point in SonarQube Cloud's analysis history (/api/project_analyses/search), not just a re-dated copy of the latest one. A non-main branch's historical points perform the same "Create analysis" handshake (see TRANSFER-INTERNALS.md) the regular current-snapshot import already performs once per report — once per historical point here, not once per branch, mirroring what a real scanner does before every analysis upload.

Each historical entry carries the project's own measures as recorded by the source server at that analysis: lines of code, complexity, comment density, duplication, and — since #557 — coverage itself, not just its raw inputs. The Compute Engine only ever computes coverage from a component's real per-line coverage data, never from a pushed aggregate measure (confirmed live: pushing lines_to_cover etc. as plain measures, #557's first attempt, was silently ignored), so each historical point's placeholder file carries synthetic per-line coverage records — arbitrary which lines are marked covered, since the file is never viewed, but built so the totals match the source's real figures exactly.

Duplication turned out to hide the identical bug. duplicated_lines, duplicated_blocks and duplicated_files were also silently ignored when pushed as plain measures, so duplicated_lines_density (itself a formula over duplicated_lines/lines) never actually computed — invisible until now because the reference project used to validate this PoC has had 0% duplication for its entire history, so "shows 0" looked correct without being computed at all. Fixed the same way as coverage: each historical point's placeholder file also carries a synthetic same-file duplication block — one origin line range and one duplicate line range within that same placeholder file, since there is no second real file to duplicate against — sized so the reconstructed duplicated_lines/duplicated_blocks/ duplicated_files match the source's real figures exactly.

Historical points now also carry real bugs, vulnerabilities and code_smells counts, reliability_rating/security_rating, and technical debt (sqale_index) — reconstructed the same way coverage and duplication are: the Compute Engine derives all of these exclusively by counting real Issue-shaped entries in the submitted report, never from a pushed aggregate measure. So for each historical point the tool fabricates that many placeholder issues — one per bug/vulnerability/code smell the source recorded at that analysis — submitted through SonarQube Cloud's external-issue mechanism (for third-party/ad-hoc findings, distinct from native rule-based issues) under fixed ad-hoc rules (smt-history:bug, smt-history:vulnerability, smt-history:code_smell). These are exactly as synthetic as the placeholder file itself: no real code location or message, just whatever severity reproduces the source's real historical rating (ratings threshold on the single worst severity present, not an average, so one issue at the right severity is enough to make this exact rather than approximate) and, for code smells, however much effort reproduces the real sqale_index (confirmed live that only code-smell effort drives sqale_index, not bug/vulnerability effort). External issues were chosen specifically because, unlike native issues, they need no active rule in the target's resolved quality profile.

What still can't come across is the original issues themselves — their file, line, message and identity — and Security Hotspots. Both remain a hard SonarQube API limitation, not a scope choice: there is no API that returns "what issues existed as of a past analysis," so the real, individual findings cannot be reconstructed for a historical point — only the aggregate counts/ratings/debt above, which the source does expose historically, via the same measures-history endpoint already used for coverage/ncloc/etc. Hotspots specifically stay unreconstructed even in aggregate: unlike bugs/vulnerabilities/code smells, converting a hotspot to an issue needs a real active rule in the target's resolved quality profile — exactly the constraint the external-issue mechanism above exists to sidestep, which is why it can't be reused for hotspots. SonarQube also only keeps issues (and hotspots) attached to a branch's most recent analysis anyway, which is exactly what the existing, unchanged current-snapshot import already migrates in full.

Two flags can bound how much history is walked, applied to each migrated branch's own full analysis list on the source (per branch, not once per project), oldest to newest, always dropping that branch's single most recent analysis (already covered by the current-snapshot import). Left unset, both default to 0 — no cap, no spacing — so every analysis becomes a candidate:

Flag Default Meaning
--history_max_points 0 At most this many historical snapshots per branch — each migrated branch gets its own cap, so a project with N migrated branches can replay up to N × this many; 0 (the default, whether passed explicitly or left unset) means no cap — every candidate analysis is migrated. When set above 0 and a branch has more candidates than that after interval bounding, they are evenly resampled across that branch's whole history span, not just the oldest end.
--history_min_interval_days 0 Two selected snapshots of the same branch are never closer together than this; 0 (the default, whether passed explicitly or left unset) means no spacing rule at all — every analysis in a branch's source history becomes a candidate, including several on the same day at different times.

By default, both flags are left unset — and an unset flag now behaves exactly like explicitly passing 0: every analysis on each migrated branch of the source becomes a history candidate, with no cap and no minimum spacing. These flags exist for callers who want to dial history down from that exhaustive default to something bounded or spaced out, not the other way around — there is nothing "denser" than the default to opt into. Here's what dialing the spacing up costs you, on a source branch with 134 analyses spanning 2021→2026:

--history_min_interval_days Points selected
0 133 (every analysis but the newest)
1 105
7 71
30 31

Bear in mind each point is a separate report submission plus a Compute Engine poll — roughly 11.5s (the CE poll interval is 10s, #571) — so the exhaustive default is a long migration on a project with a lot of history; use --history_min_interval_days and/or --history_max_points to bound it deliberately.

# Migrate the current snapshot as usual, plus every historical analysis on
# every migrated branch — unbounded, unspaced (the default when the flags are left unset)
sonar-migration-tool transfer -c config.json --project_key_regexp <projectKeyRegexp> \
  --migrate_history

# Bounded history instead: at most 10 points per branch, at least 30 days apart
sonar-migration-tool transfer -c config.json --project_key_regexp <projectKeyRegexp> \
  --migrate_history --history_max_points 10 --history_min_interval_days 30

Known limitations (PoC):

  • Best-effort, not transactional. If a historical submission is rejected by the Compute Engine, history migration for that branch stops and logs a warning — it never fails or blocks the regular current-snapshot import that follows it.

  • Resume-safe. A point dated at or before the branch's newest analysis on the target, or at or after the date the regular import is stamped with, is dropped before submission and logged at Info: the Compute Engine refuses both, and the second would make the regular import itself fail. A re-run therefore only sends what the target is missing, and a branch that is already up to date is skipped entirely.

  • Not every migrated point stays on the target. The Compute Engine accepts and writes every point the migration submits, but SonarQube Cloud then removes some of them. Expect the Activity page to hold fewer analyses than were migrated — on a 134-analysis source project, 133 points were submitted, all 133 were accepted, and 61 remain.

    This is target-side behaviour, not a migration failure, and it was confirmed by direct observation rather than inferred. A throwaway project was seeded with exactly one point dated 2021-06-13 and polled sub-second:

    15:24:14Z      CE task → SUCCESS
    15:24:14.437Z  ┐ 12 consecutive HTTP-200 polls of
                   │ /api/project_analyses/search list the analysis, and
    15:24:29.659Z  ┘ /api/qualitygates/project_status?analysisId=… returns 200
    15:24:31Z      the next CE task (the regular current-snapshot import) runs
    15:24:31.203Z  same endpoint, same query → the analysis is gone,
                   and its analysisId returns 404 permanently
    

    A control run, killed before any subsequent analysis could succeed, still holds its 2021-06-13 analysis. So the row is created and readable, then deleted — it is not rejected at ingestion, and it is not merely hidden by the API.

    What decides which points are removed is not established. The oldest points go first, which is consistent with the documented housekeeping retention window, but the source used for testing has an 87-day gap around the apparent boundary, so the data cannot distinguish 260 weeks from 5 calendar years — or from any other value in between. Removals were also observed well inside any retention window. Treat the surviving count as something to measure on your own target, not to predict.

    Practically: migrating more points always costs proportional wall clock, and past some point the target discards the extra — so if you don't need exhaustive history, dial down with --history_min_interval_days and/or --history_max_points rather than paying for points that won't survive.

  • Each historical entry carries one placeholder file. A report holding a lone project component with a raw measure is rejected by the Compute Engine, so every historical analysis includes a single empty __history_snapshot__.<ext> component for the measures to attach to. Its language is chosen from the ones the target organization actually has a quality profile for. The file is never meant to be read, but it is part of the analysis.

  • Requires both extract and migrate to have --migrate_history (or the migrate_history config key) set — transfer sets both automatically; running the two commands separately needs the flag on each.


Output

  • Intermediate files — written to --export_dir (default ./migration-files/). Same files as the manual workflow: organizations.csv, gates.csv, profiles.csv, groups.csv, templates.csv, portfolios.csv.
  • migration_summary.pdf — PDF migration summary, written to the export directory on completion.
  • migration_summary.md — Markdown rendering of the same summary, written alongside the PDF on completion.
  • run_meta.json — per-phase / per-task timing and overall_status (success | partial | failed) for the run, written to the run directory on completion (including failed runs, so the summary can explain the failure).
  • run_events.jsonl — JSON Lines stream of run events (one record per line) mirrored from the logger by the tee slog handler; the summary collector parses these to build the report.
  • Stdout — every command prints See sonar-migration-tool output results in <directory> when it finishes so you always know where to look.

For a full description of every output file, see the Output Files Reference in MIGRATE.md.


After the transfer

  1. Log in to SonarQube Cloud and confirm the project appears under the target organization.
  2. Spot-check that the quality gate and quality profile are present.
  3. Spot-check that issues came across (compare counts against the source). Former Security Hotspots are part of that issue count — filter the target project by the sqs-hotspot tag to see them. Do not compare against SonarQube Cloud's Security Hotspots view: it is empty by design, because Cloud no longer has hotspots. Project data is always imported, so a fresh re-scan is not required to seed historical data — though you should still run a normal analysis once your pipeline is repointed.
  4. Update your CI/CD pipeline to point at SonarQube Cloud (SONAR_TOKEN, SONAR_HOST_URL).

For more on post-migration steps, see the After you migrate section in MIGRATE.md.


Troubleshooting

  • Token errors — see the Token permissions section in MIGRATE.md.
  • Org not found — confirm --default_organization matches an existing organization in your SonarQube Cloud enterprise.
  • Anything else — TROUBLESHOOTING.md has the full list of common errors.