Migration
Migrating Repositories
Repository Migration Overview
Before pipelines can move to GitHub Actions, the code they build has to live on GitHub. Migration Assistant can do that move for you: connect an Azure DevOps organization, browse every repository CodeCargo can see, and copy the ones you choose to your GitHub organization through GitHub Enterprise Importer — the same engine behind gh ado2gh migrate-repo, driven from the app instead of a terminal.
Each successful copy is recorded as that repository's relocation, so the pipelines you import link to the new GitHub repository automatically and Prepare Rewire knows where everything went. Repositories migrated outside CodeCargo with gh ado2gh are still recognized once discovery traces their Migration Log issue.
The work happens in the Migrate group of the organization sidebar:
- Connections - The source systems CodeCargo is connected to, and where you connect a new one
- Repositories - The connected estate, with tabs for Repositories, Migrations, and Custom properties
- Pipelines - The pipeline-side dashboard with its Sources, Batches, and Waves tabs, described on the Migration Assistant overview
Who can do what
Anyone with Migration Assistant access can browse connections, repositories, and migration history. Connecting or disconnecting a source system requires organization admin permission, and creating, starting, stopping, or retrying a migration requires the MigrationAssistantMigrateRepositories permission, which is granted to the Admin role only. See Access Control.
Connecting Azure DevOps
Open Migrate → Connections and click Add connection, then choose Azure DevOps. There are two ways to connect an organization.
Sign in with Microsoft
This is the recommended path. You sign in with an Azure DevOps account that administers the organizations you want to connect, and CodeCargo gets its own identity in each of them:
- Sign in with Microsoft and allow CodeCargo to access Azure DevOps. The first time anyone in your Microsoft Entra tenant connects, an Entra administrator approves CodeCargo for the tenant.
- Pick the organizations and projects to connect. Your Microsoft account's organizations are listed; select the ones to connect, then choose which of their projects CodeCargo can read. Nothing changes until you confirm.
- Review what changes in Azure DevOps. The confirmation step lists the organizations being connected and the projects gaining or losing access. Only a Project Collection Administrator of each organization can make these changes.
After setup, CodeCargo works under its own account rather than your sign-in. It appears as a user in each connected organization and holds one Basic license there. Your sign-in is used only during setup and is never stored.
Connect with a personal access token
If nobody can approve Microsoft apps for your tenant, click Can't approve Microsoft apps? Connect with a personal access token instead. Enter the organization name, an Azure DevOps personal access token with read access to Project and team, Code, and Build, and optionally the token's expiry date. CodeCargo checks the token, lists the projects it can see, and connects only the projects you select.
The token acts as you. CodeCargo stores it encrypted and cannot revoke it — disconnecting wipes the stored copy, and you delete the token in Azure DevOps yourself. Azure DevOps does not report token expiry, so the date you enter is shown on the connection page purely so you can renew the token in time.
Managing a connection
The connection page shows who connected it, when, the Entra tenant, and each linked organization with its connected projects. From here you can:
- Add organizations or projects - Starts another Microsoft sign-in so you can extend access. Projects added in Azure DevOps later are not picked up automatically; add them here.
- Change projects - On a token-connected organization, reselect the projects CodeCargo reads.
- Disconnect an organization - Removes the CodeCargo user from that organization (a Project Collection Administrator must do this; if Azure DevOps refuses, the organization stays connected) or, for a token-connected organization, wipes the stored token.
Importing pipeline definitions is separate from connecting: it still runs from your terminal with the importer CLI, and the connection page links to the manual importer.
The Repositories Tab
Migrate → Repositories lists every repository CodeCargo can see through its connections, with project, size, pipeline count, and status. Click Refresh to take a fresh inventory of projects, repositories, and pipeline counts; a refresh also runs automatically before a migration starts.
Narrow the table with the Location filter (source system → organization → project), the Status filter, and the search box. Disconnected repositories are hidden by default; tick Show disconnected repositories to include them.
| Status | Meaning |
|---|---|
| Not migrated | No migration is on record |
| Migrated | A copy exists on GitHub. Hover for the date and migration number; the link beside it opens the GitHub copy |
| Disabled | Disabled in Azure DevOps. Enable it there before migrating |
| No commits | The repository is empty, so there is nothing for the importer to copy |
| Missing | The repository disappeared from the source since the last inventory |
| Not connected | Its project or organization was disconnected in CodeCargo. Nothing changed at the source |
| Fork | Shown alongside the other statuses for repositories that are forks in the source |
A repository that is already in a migration shows In migration #N (or (draft)) and cannot be selected for another one. A repository may be in one migration at a time.
Custom Properties
The Custom properties tab holds the organization's mapping from source details to GitHub custom properties. A migration with Stamp custom properties on the GitHub copy enabled (the default when a mapping exists) writes the mapped fields on each GitHub copy, so anyone looking at the GitHub repository can trace it back to where it came from. Nothing is stamped until the organization saves a mapping, and a migration can turn stamping off in its settings; its review then reads "Not stamped".
Azure DevOps offers these fields. The first four carry a suggested standard property name and show a Standard badge while they keep it; the overflow menu's Restore standard mappings adds all four at once.
| Source field | Standard property name | Value |
|---|---|---|
| Organization | ado-org | The Azure DevOps organization the repository came from |
| Project | ado-project | The project the repository sits in |
| Repository | ado-repo | The repository's name in Azure DevOps |
| Repository id | ado-repo-uuid | The repository's Azure DevOps GUID, which survives a rename |
| Organization id | (choose a name) | The organization's account name |
| Project id | (choose a name) | The project's GUID |
| Repository URL | (choose a name) | The repository's web address in Azure DevOps |
| Default branch | (choose a name) | The repository's default branch |
| Source system | (choose a name) | azure-devops |
Rename a property, remove one, or Add mapping to include more fields; the overflow menu restores the standard mappings or removes them all. Property names are validated as you type, and CodeCargo reads your GitHub organization's existing property schema so it can warn before a save would redefine a property of a different type.
The mapping applies to the whole organization, and each migration keeps a copy of it from the moment it starts — editing the mapping mid-run does not change what a running migration stamps. Saving does not change repositories that have already been migrated, and never removes a property from your GitHub organization.
Creating a Migration
Select repositories on the Repositories tab and click Migrate selected…. A migration copies from one organization, so the selection must sit within a single one. Choose New migration, or — when a draft already exists for that organization — Add to migration #N.
A new migration asks for a Name (it defaults to "Migration #N"), whether to stamp custom properties on the GitHub copy, and a default Visibility for the targets — Same as the source, Private, or Public. These settings stay editable from the draft's Settings button until the migration starts; repositories added later follow them, while names already proposed keep what they have.
Each draft has its own page with three stages. The page saves as you go — there is no Save button — and a status line beside the stage strip reads "Saving…" or "All changes saved". Landing on the page opens the first stage that still needs work; Continue walks to the next stage in order.
Stage 1 — Repositories
The draft's repositories, with project, size, pipeline count, and a Remove action per row. Click Add repositories to open the inventory in a side sheet and add more; the sheet can also show disconnected, already-in-a-migration, and already-migrated repositories, though none of those can be selected. A migration holds up to 2,000 repositories.
Each row shows Ready, Checking… while its pre-migration checks run, or a reason it cannot be migrated:
- Disabled in Azure DevOps
- Already migrated
- No commits - Empty repositories are refused when you try to add them
- Over GitHub's 40 GiB limit
- A branch name over Git's 255-byte limit - Rename or delete the branch in Azure DevOps first
- No longer in the inventory or Source no longer connected
Pre-migration checks — branch names and Git LFS usage — are read once at inventory time for the repositories a draft holds, so adding repositories never waits on the source. Repositories that use Git LFS carry a warning: they migrate, but need a follow-up step afterwards (see After the Migration).
Stage 2 — Names and Visibility
Every row gets a proposed GitHub name and visibility, and the Check column says whether it is ready:
- Adjusted from X - Characters GitHub does not allow were replaced. Names may use letters, digits,
.,-, and_; anything else becomes-. - Renamed from X: taken on GitHub or … another repository in this migration has that name - The default is the Azure DevOps repository name; when that clashes with a repository already in your GitHub organization or another row in the migration, the row falls back to
{project}-{repo}. - Duplicate in this migration - Two rows would create the same GitHub name. Fix one before starting.
Type in a row's GitHub name to override it; edits are re-checked against your GitHub organization's inventory and the rest of the migration as you type. Enter moves to the next row, Shift+Enter to the previous one, and Escape restores the proposed name. Use the All / Needs a look / Renamed filter to focus on the rows worth reviewing.
To rename in bulk, enter a naming pattern built from {repo}, {project}, and {org} and click Apply. Applying a pattern keeps any names you typed by hand and offers to reset those too. An empty pattern keeps the source name.
Visibility mirrors the Azure DevOps project's visibility by default (a public project gives a public repository, anything else private). Change a single row, or use Set all visibility to choose Private, Public, or Match the project for every row. Internal visibility is not offered.
Stage 3 — Review and Start
The last stage is locked until the earlier ones are settled, and says which stage still needs attention. Once ready, the Review card summarizes the migration:
- Repositories - Count and total size
- GitHub owner - The organization the copies are created in
- Repositories copied at once - How many copies CodeCargo keeps in flight, from 1 to 20 (default 5). GitHub Enterprise Importer copies one repository per request and applies undocumented organization-level limits, so a higher number is not always faster.
- Custom properties - The property names that will be stamped, or "Not stamped"
- After each copy succeeds - The post-migration steps the migration will run
Beside it, the Required to start card names the two credentials the importer needs. GitHub Enterprise Importer copies with its own credentials, which CodeCargo cannot mint on your behalf, so a migration collects both from you at the moment it starts:
GitHub personal access token (classic)
- From an owner of the GitHub organization, or an account that has been granted the migrator role
- Scopes:
repo,workflow, andadmin:org - Must be a classic token, not a fine-grained one. If your organization uses SAML single sign-on, authorize the token for it after creating it.
- Also used for post-migration configuration, such as stamping custom properties
- Kept in CodeCargo for up to seven days after the migration finishes so you can retry failures, then deleted. Revoke it on GitHub when you are done.
Azure DevOps personal access token
- Organization-scoped for the organization being migrated, read-only
- Scopes: Code (read), Work Items (read), Identity (read), and Project and Team (read). The importer rejects a narrower token, even though this set can read more than the repositories.
- Kept in CodeCargo for up to seven days after the migration finishes, then deleted. CodeCargo cannot revoke an Azure DevOps token; delete it in Azure DevOps when you are done.
Each credential has a Create one link to the page where it is minted and a Requirements tip with the same details.
Click Start migration to enter both tokens. The GitHub token is checked as you type — CodeCargo reports the account it belongs to, its expiry, missing scopes, and whether the account is an owner of the organization. A member who is not an owner gets a warning rather than a refusal, with the gh gei grant-migrator-role command an owner can run to grant the migrator role, since a granted role cannot be queried. The Azure DevOps token is checked against the organization when the migration starts. Neither token is saved with the draft.
Refusals send you to the right stage
Everything is re-checked at start. If something changed since you reviewed — a repository was disabled, a name was taken on GitHub, a token was rejected — the migration is not started; every affected row is marked inline on the stage that can fix it, and the page switches there. Nothing is queued unless every check passes.
The Migration Run
Once started, the draft's page becomes the run page at the same URL. The header shows the migration's status, when it started, how long it has been running, and when CodeCargo last checked with GitHub; a progress bar counts repositories succeeded, in progress, failed, skipped, and queued. The page refreshes itself as the run progresses.
| Run status | Meaning |
|---|---|
| Draft | Staged but not started |
| Validating | Tokens and repositories are being checked before anything is queued |
| Running | Copies are being queued and polled |
| Stopping | Stop was requested; queued items are being aborted and in-progress ones left to finish |
| Completed | Every repository was copied |
| Completed with failures | The run finished, but some repositories failed or were skipped |
| Stopped | The run was stopped |
| Abandoned | The run was abandoned while validating |
Each repository row moves through Pending → Queued → In progress → Succeeded → Done (once post-migration steps finish), or ends Failed, Aborted, Skipped, or Preflight failed. Expand a row to see its GitHub migration id, why it failed, any warnings GitHub reported, and the migration log. CodeCargo captures the log when a copy finishes because GitHub deletes it 24 hours after completion; the captured log opens in a side sheet, along with a link to the Migration Log issue on GitHub.
Run actions
- Stop migration - Available while running. Nothing new is queued, queued copies are aborted, and copies already in progress are left to finish so the record is complete.
- Abandon migration - Available while validating, if the run cannot proceed.
- Retry / Skip - On each failed row, and Retry N failed for all of them. A note under the buttons says whether retrying is likely to help ("Retrying alone will not help" or "Retry after fixing the source"); Retry stays available regardless, since you may have fixed something CodeCargo cannot see. If a failed attempt left an empty repository on GitHub that still holds the target name, the retry reports it so you can delete that repository before trying again.
- Revoke token - Available the whole time CodeCargo holds the GitHub token, running included, so you never have to wait for a migration to end to get your token back.
If the GitHub rate limit is hit, the page says so and queueing resumes on its own.
Tokens after the run
CodeCargo keeps its copies of both tokens for up to seven days after the migration finishes, so failures can be retried and post-migration steps can complete. Two different things can end that hold, and the run's header says which one happened:
- You revoke the GitHub token. Click Revoke token on the run page. CodeCargo asks GitHub to revoke the token, then deletes both copies; the header reads "GitHub token revoked and both copies deleted from CodeCargo". If GitHub refuses the revocation, the header says so and offers to try again, or you can revoke the token on GitHub yourself.
- The hold expires. After seven days CodeCargo deletes its copies without contacting GitHub; the header reads "copies deleted from CodeCargo; the GitHub token was not revoked and is still valid on GitHub". CodeCargo never revokes a token on its own, so revoke it on GitHub when you are done.
CodeCargo cannot revoke an Azure DevOps token in either case; deleting the copy is all it can do. Delete the token in Azure DevOps yourself.
Retrying an item after the copies are gone asks for both tokens again.
After the Migration
When a copy succeeds, CodeCargo:
- Marks the source repository as migrated on the Repositories tab, with a link to the GitHub copy
- Syncs and activates the GitHub repository in CodeCargo, so it is ready for workspaces and workflows. If your CodeCargo GitHub App installation is limited to selected repositories, add the new one to it; the remaining steps wait until it is visible.
- Stamps custom properties from the mapping the run snapshotted when it started, if the migration has stamping enabled
- Records the relocation from the Azure DevOps location to the GitHub repository. Imported pipelines for that repository link to the new home, and Prepare Rewire treats the migration exactly like one traced from a Migration Log issue.
GitHub Enterprise Importer copies Git data, pull requests, and most metadata, but not everything. Warnings from GitHub — oversized metadata, comments outside a diff — are shown on the row with the detail in the captured log.
Git LFS objects are not migrated
The importer copies a repository's LFS pointer files but not the LFS objects behind them. Repositories that use Git LFS are flagged in the draft and again on the finished run under Post-migration action required. For each one, fetch the objects from Azure DevOps and push them to GitHub:
git lfs fetch --all
git lfs push --all <github-remote>
The Migrations Tab
Migrate → Repositories → Migrations lists every migration in the organization — drafts, running migrations, and finished ones — with the source organization, repository counts, who started it, when, how long it took, its status, and its token state. Open a draft to continue it, or delete it to free its repositories for another migration.
Next Step
With repositories on GitHub, import their pipeline definitions if you have not already, then map and migrate the pipelines themselves. When the pipelines are ready, finish with Prepare Rewire.
