Migration playbook · Azure DevOps

How to migrate from Azure DevOps to GitHub

Move Azure Repos to GitHub Enterprise Cloud with GitHub Enterprise Importer, keep Azure Boards and Pipelines connected or convert pipelines to GitHub Actions, then lock the old repositories.

Updated 4 Nov 20267 phases22 detailed steps11 official sources
Start here

Your migration plan

This moves Azure Repos Git repositories, with their history and pull requests, from Azure DevOps to GitHub Enterprise Cloud. GitHub Enterprise Importer (GEI) copies each repository in a single run while work on it is frozen. Azure Boards and Azure Pipelines can stay where they are and connect to GitHub, so you convert pipelines to GitHub Actions later, team by team. Migration time depends mostly on how many pull requests each repository has, and trial runs show you the real figure.

Recommended routeGitHub Enterprise Importer (gh ado2gh) for repositories, keeping Boards and Pipelines connected

The smallest change for developers on day one: generate-script --all rewires pipelines to GitHub and connects Azure Boards for you.

Other options and when to use them
  • GEI plus GitHub Actions Importer in the same move: Teams want to leave Azure Pipelines as part of the move and can accept a longer cutover.
  • Mirror push (code only): Your source is Azure DevOps Server or your destination is GitHub Enterprise Server, or you have only a few repositories.
Order to migrate in, and why

Set up access first, prove the method in a trial, then move repositories in waves. Pipelines and Boards stay connected on day one, so developers change only where their code lives. Pipeline conversion comes later, team by team, and old repositories are disabled last.

  1. Source check, tokens and the inventory reportConfirms GEI can read your source at all, and the pull request counts in the report decide how long each repository takes.
  2. GitHub organisation, accounts and the repository mappingNames, teams and accounts must exist before the script runs and before mannequins can be reclaimed.
  3. Shrink oversized repositoriesGEI refuses repositories and files over its limits. Rewriting history before the move means everyone re-clones once.
  4. Trial of one repository into a sandbox organisationShows timings and follow-up work without stopping anyone's work.
  5. Production waves, busiest repositories in their own windowThere is no top-up copy, so each wave is frozen while it runs. Repositories with many pull requests take longest.
  6. Per wave: mannequins, LFS objects, team members and Boards linksUntil these are done, history shows placeholders, builds that need large files fail and nobody but admins has access.
  7. Pipelines: rewired first, converted to GitHub Actions laterRewiring keeps builds running on day one. Converting team by team keeps each change small and testable.
  8. Developers switch remotes, then old repositories are disabledRewired pipelines and rollback both need the old repositories readable until GitHub is proven.
Settings checklist: decide these before you copy

Built-in and third-party tools treat these differently. Tick each one off once you have decided.

Download this checklist (Excel)
  • How each tool handles it
    Built-in methodGEI: migrates only from Azure DevOps Services, not Azure DevOps Server. TFVC repositories (Team Foundation Version Control, the older non-Git kind) do not migrate: convert them to Git first.
    Mirror push (git push --mirror)Works from any Git server, including Azure DevOps Server. A mirrored clone includes all branches and tags, and nothing else.

    Tip: Run gh ado2gh inventory-report per organisation and mark any TFVC repositories straight away.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: 40 GiB of source code per repository (public preview) and 400 MiB per file during migration. After migration GitHub limits files to 100 MiB, and single commits and pushes to 2 GiB.
    Git LFS (git lfs migrate)Moves large files already in history into Git LFS, or git filter-repo removes them. Both rewrite history, so every clone must be re-cloned.

    Tip: Run git-sizer on the largest repositories before the trial.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: pull requests migrate with their user history, files attached to them and links to work items. Migration time depends largely on how many pull requests a repository has.
    Mirror push (git push --mirror)Copies Git data only. Pull requests stay in Azure Repos.

    Tip: Sort repos.csv by pull request count and give the busiest repositories their own window.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: repository branch policies migrate, except user-scoped and cross-repository policies.

    Tip: List user-scoped and cross-repository policies in the trial and recreate them as GitHub rulesets after each wave.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: work items stay in Azure Boards, and links between pull requests and work items migrate. The generated script with --all also configures the Azure Boards connection. AB# links (AB#123 in a commit or pull request) go to the wrong work item if a repository is connected to more than one Azure DevOps organisation.

    Tip: Connect each GitHub repository to one Azure DevOps organisation only.

    GitHub Docs: Migrate repositories from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: pipelines do not migrate. The generated script with --all rewires existing Azure Pipelines to build from the new GitHub repositories.
    GitHub Actions ImporterConverts Azure Pipelines to GitHub Actions workflows and opens a pull request. Secrets, service connections, unknown tasks and some triggers and gates still need manual work.

    Tip: Convert simple build pipelines first, and leave release pipelines with gates and approvals until last.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: repository permissions do not migrate. The generated script with --all creates teams, which start empty.

    Tip: Export who has access to each team project before the wave, so you can add members the same day.

    GitHub Docs: Migrate repositories from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: pull requests and comments are tied to mannequins (placeholder users). Content linked to a mannequin may not show in searches until you reclaim it.

    Tip: Make sure every developer has a GitHub account in your enterprise before the first wave.

    GitHub Docs: Reclaiming mannequins for GitHub Enterprise Importer
  • How each tool handles it
    Built-in methodGEI: migrates repositories that use LFS, but not the LFS objects themselves. Push them as a follow-up task.
    Mirror push (git push --mirror)Also leaves LFS objects behind. GitHub's steps fetch them with git lfs fetch --all and push them with git lfs push --all as separate commands.

    Tip: Find LFS repositories by looking for filter=lfs in .gitattributes.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: no delta migrations (a top-up copy of only what changed). Changes made during or after the run must be moved by hand. GitHub recommends, but does not require, halting work during the production run.

    Tip: Remove push rights for the freeze, and disable a repository only after its migration succeeded and pipelines build from GitHub, because a disabled repository cannot be read.

    GitHub Docs: Migrate repositories from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: by default the script gives the new repository the same visibility as the source repository. The --target-repo-visibility option changes it.

    Tip: Check the script before you run it, so nothing becomes public by mistake.

    GitHub Docs: Migrate repositories from Azure DevOps to GitHub
  • How each tool handles it
    Built-in methodGEI: accepts only classic personal access tokens, not fine-grained ones. A GitHub IP allow list must include GEI's address ranges, and an identity provider IP allow list (such as a Microsoft Entra Conditional Access policy) can block it.

    Tip: Set both tokens to expire just after your last wave, and agree allow list handling with your identity team before the trial.

    GitHub Docs: Managing access for a migration from Azure DevOps
  • How each tool handles it
    Built-in methodGEI: organisation rulesets can make a migration fail when commits break them, for example commit author emails outside a required domain.

    Tip: Migrate where blocking rulesets do not apply to the new repositories, and turn them on afterwards.

    GitHub Docs: Understand migrations from Azure DevOps to GitHub
The plan, phase by phase
  1. 1

    Get ready

    Before you start

    Watch out: GEI does not migrate from Azure DevOps Server.

    Ways around it

    • Move to Azure DevOps Services first, or use a mirror push for code only.

    Watch out: GEI accepts only classic tokens, and an identity provider IP allow list (such as an Entra Conditional Access policy) can block it.

    Ways around it

    • Allow classic tokens for the migration, add GEI's ranges to any GitHub IP allow list, and agree allow list handling with your identity team.

    Watch out: TFVC repositories (Team Foundation Version Control, the older non-Git kind) do not migrate.

    Ways around it

    • Convert them to Git first. GitHub documents a TFVC import route.
    Show the detailed steps (2) →
  2. 2

    Plan the waves

    Watch out: GEI stops at 40 GiB of source code per repository and 400 MiB per file during migration.

    Ways around it

    • Run git-sizer on the largest repositories before the trial.

    Tools that can do it

    • Git LFS (git lfs migrate)Moves large files already in history into Git LFS, or git filter-repo removes them. Both rewrite history, so every clone must be re-cloned.

    Watch out: Organisation rulesets such as commit author email rules can make migrations fail.

    Ways around it

    • Migrate where blocking rulesets do not apply to the new repositories, and turn them on afterwards.
    Show the detailed steps (3) →
  3. 3

    Run a trial

    Show the detailed steps (3) →
  4. 4

    Migrate production

    In each wave's freeze window

    Watch out: GEI has no delta migration (a top-up copy of only what changed), so changes made during or after the run must be moved by hand.

    Ways around it

    • Lock each wave's repositories for the whole run.

    Watch out: A disabled repository cannot be read, so pipelines that still need it fail.

    Ways around it

    • Remove push rights during the run, and disable the repository only after a successful migration.

    Watch out: GEI migrates branch policies, except user-scoped and cross-repository ones.

    Ways around it

    • Recreate them as GitHub rulesets.
    Show the detailed steps (4) →
  5. 5

    Finish people and Boards

    Watch out: Repository permissions do not migrate, and the teams the CLI creates start empty.

    Ways around it

    • Export who has access to each team project and add them, or link the teams to Entra ID groups.

    Watch out: GEI migrates the repository but not its LFS objects, so builds that need them fail.

    Ways around it

    • Mirror-clone from Azure Repos, run git lfs fetch --all, then git lfs push --all to GitHub.

    Watch out: Work items stay in Azure Boards, and AB# links go wrong if a repository is connected to more than one Azure DevOps organisation.

    Ways around it

    • Keep Boards connected to GitHub, with each repository connected to one organisation only.
    Show the detailed steps (4) →
  6. 6

    Sort out pipelines

    Watch out: GEI does not migrate pipelines.

    Ways around it

    • Keep Azure Pipelines rewired to GitHub for the first waves.
    • Convert simple pipelines first, and leave release pipelines with gates and approvals until last.

    Tools that can do it

    • GitHub Actions ImporterConverts Azure Pipelines to workflows. Secrets, service connections, unknown tasks and some triggers and gates still need manual work.
    Show the detailed steps (4) →
  7. 7

    Switch and disable

    Watch out: Deleting Azure Repos repositories, pipelines (with their run history) or the Azure DevOps organisation cannot be undone.

    Ways around it

    • Disable rather than delete: a disabled repository can be re-enabled.
    • To roll back, re-enable Azure Repos and push back any commits made on GitHub.

    Turn this phase into dated tasks with the cutover planner

    Show the detailed steps (2) →
You are finished when
  • Branch, tag and commit counts match, and the default branch head is the same commit on both sides.
  • Pull request counts match repos.csv, and no current staff remain as unclaimed mannequins.
  • Pipelines or workflows build from GitHub, and AB# links show on work items.
  • Direct pushes to the default branch are refused, and the old repositories show as disabled.

Tools named in this plan are independent third-party products, not endorsed by the platform vendor and not paid placements. Check the tool's own documentation and run a pilot first.

Click by click

Detailed steps

Open a part when you reach it in the plan. Each step says where to go, what to do and what you should see.

About this migrationWhat it covers, limits at a glance

GitHub Enterprise Importer (GEI) moves Azure Repos Git repositories from Azure DevOps Services to GitHub Enterprise Cloud, with commit history and pull requests. Work items stay in Azure Boards, and pipelines stay in Azure Pipelines unless you convert them. Many teams keep Boards and Pipelines running against GitHub for a while, then move CI to GitHub Actions with GitHub Actions Importer.

The order of work: inventory and plan, set up access and the CLI, trial in a sandbox organisation, migrate production with work halted, then do the follow-up tasks (people, LFS, teams, Boards, Pipelines) and disable the old repositories. For the same job from GitLab, see the GitLab to GitHub guide.

Native toolGitHub Enterprise Importer with the ADO2GH extension of the GitHub CLI (gh ado2gh)
SourceAzure DevOps Services (cloud) only; Azure DevOps Server must move to Services first
MigratedGit history, pull requests with history, work item links and attachments on pull requests, most branch policies
Not migratedWork items, pipelines, Git LFS objects, repository permissions, user-scoped and cross-repo branch policies
Boards and PipelinesCan stay in Azure DevOps and connect to GitHub; generate-script --all rewires pipelines and sets up Boards integration
Size limit40 GiB per repository (public preview); 400 MiB per file during migration
1What you need before step one
NeedDetailsWhere to get it
Azure DevOps ServicesGEI migrates from Azure DevOps Services only, not Azure DevOps Server.Move Azure DevOps Server to Services first using Microsoft's migration guidance
GitHub Enterprise CloudAn enterprise account on GitHub.com or GHE.com with the destination organisation.GitHub sales or your partner; check current pricing
GitHub role and tokenOrganisation owner, or migrator role granted by an owner. Classic token: owner repo, workflow, admin:org; migrator repo, workflow, read:org.GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic)
Azure DevOps tokenWork Items (Read), Code (Read) and Identity (Read) at minimum; GitHub recommends full access for inventory-report, pipeline rewiring and Boards. Allow access to all organisations if you migrate several.Azure DevOps (https://dev.azure.com/contoso) > User settings > Personal access tokens
GitHub CLIVersion 2.4.0 or later with the ADO2GH extension.https://cli.github.com
PowerShellThe generated migration script is PowerShell; macOS and Linux need PowerShell 7.https://learn.microsoft.com/powershell/scripting/install/installing-powershell
Boards connection rightsProject Collection Administrator in Azure DevOps and admin or owner on the GitHub repositories.Azure DevOps organisation settings and GitHub repository settings
Docker (for CI conversion)GitHub Actions Importer runs in a container.https://www.docker.com
2Inventory and plan3 steps
Azure DevOpsMoves with GEI?What to do
Git source and historyYesCheck size limits
Pull requests with user history, work item links and attachmentsYesComplete or abandon stale pull requests first
Branch policiesYes, except user-scoped and cross-repository policiesRecreate the exceptions as rulesets
Repository permissionsNoTeams on GitHub (CLI creates Maintainers and Admins teams)
Work items (Azure Boards)NoKeep Boards and connect it to GitHub
PipelinesNo (can be rewired)Rewire to GitHub, then convert with Actions Importer
Git LFS objectsNoPush with git lfs after migration
TFVC repositoriesNoConvert to Git first (GitHub documents a TFVC import route)
  1. 1

    Run the inventory report

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    After installing the extension and setting ADO_PAT (Step 2), run the inventory report for each Azure DevOps organisation. It writes several CSV files; repos.csv lists each repository with its pull request count.

    gh ado2gh inventory-report --ado-org contoso
    You should see: repos.csv and the other CSV files appear in the current folder, with one row per repository in repos.csv.
  2. 2

    Map organisations, projects and teams

    Where: Your Identity and licence mapping sheet

    Map each Azure DevOps organisation to one GitHub organisation, each repository to a GitHub name (team project prefix if names clash), and each team project's permission groups to GitHub teams.

    You should see: Every repository has a target organisation, name and owning team.
  3. 3

    Plan waves and freeze windows

    Where: Your Migration wave plan

    Group repositories by team project or by teams that release together. Give each wave a trial date, a production date with a freeze window, and a decision for its pipelines: rewire to GitHub, or convert to Actions now.

    You should see: The wave plan is signed off by each team lead.
3Set up access and the CLI2 steps
  1. 1

    Create the tokens

    Where: GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic), and Azure DevOps (https://dev.azure.com/contoso) > User settings > Personal access tokens > New Token

    Create a GitHub classic token with repo, workflow and admin:org (owner) or repo, workflow and read:org (migrator), and authorise it for SAML single sign-on if used. In Azure DevOps create a token with at least Work Items (Read), Code (Read) and Identity (Read), or full access as GitHub recommends, with an expiry just after your last wave.

    You should see: Two tokens stored in your password manager.
  2. 2

    Install the ADO2GH extension and set the tokens

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Check gh --version is 2.4.0 or later, install the extension (GitHub updates it weekly, so upgrade before each wave) and set the tokens. For GHE.com, also set TARGET_API_URL to https://api.SUBDOMAIN.ghe.com.

    gh extension install github/gh-ado2gh
    gh extension upgrade github/gh-ado2gh
    
    # Terminal (macOS or Linux)
    export GH_PAT="ghp_your_github_classic_token"
    export ADO_PAT="your_azure_devops_token"
    
    # PowerShell
    $env:GH_PAT="ghp_your_github_classic_token"
    $env:ADO_PAT="your_azure_devops_token"
    You should see: gh ado2gh --help lists generate-script, migrate-repo and inventory-report.
4Run trial migrations3 steps
  1. 1

    Create a sandbox organisation

    Where: GitHub > your enterprise > Organizations > New organization

    Create an organisation whose name ends in -sandbox, for example contoso-sandbox, so its purpose is clear. You can delete it after the trials.

    You should see: The sandbox organisation exists and you are an owner.
  2. 2

    Migrate a single repository

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Migrate one repository into the sandbox. Work does not need to stop for a trial.

    gh ado2gh migrate-repo --ado-org contoso --ado-team-project Payments --ado-repo payments-api --github-org contoso-sandbox --github-repo payments-api
    You should see: The command reports success, and contoso-sandbox/payments-api has the code, branches and pull requests, plus a Migration Log issue.
  3. 3

    Validate the trial with users

    Where: The sandbox repository on GitHub, with the repository owner

    Ask the owner to check history, pull requests, branch policies and work item links. Confirm you can do the follow-up tasks (mannequins, LFS, teams, pipelines). Record timings and issues in the Pilot test plan, fix them, then delete the sandbox if you no longer need it.

    You should see: The owner signs off the trial and the wave timings are updated.
5Migrate production repositories4 steps
  1. 1

    Freeze the source repositories

    Where: Azure DevOps (https://dev.azure.com/contoso) > the team project > Project settings > Repos > Repositories > select the repository

    At the start of the freeze window, set the repository's permissions so contributors cannot push, or disable it (needs the Delete repository permission). A disabled repository stays in the list but cannot be read or updated, so only disable it after a successful migration if pipelines still need to read it during the run.

    You should see: Pushes to the wave's repositories are refused.
  2. 2

    Generate the migration script

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Generate a script for the organisation. --all adds pipeline rewiring, team creation and Azure Boards integration; --download-migration-logs downloads each repository's log. Open the script, delete or comment out repositories outside this wave, and edit --target-repo and --target-repo-visibility if needed (visibility matches the source by default). For GHE.com add --target-api-url.

    gh ado2gh generate-script --ado-org contoso --github-org contoso --output migrate-wave1.ps1 --all --download-migration-logs
    You should see: migrate-wave1.ps1 contains migration steps only for the repositories in this wave, plus the team, pipeline and Boards lines.
  3. 3

    Run the script

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Run the script and keep the console output.

    ./migrate-wave1.ps1      # Terminal (PowerShell 7 installed as pwsh)
    .\migrate-wave1.ps1     # PowerShell
    You should see: Each repository reports success and a log file is downloaded for each.
  4. 4

    Read the migration logs

    Where: The downloaded log files, and the Migration Log issue in each destination repository

    Check each log for warnings and skipped items, for example branch policies that could not be migrated.

    You should see: No errors, and warnings only for items you listed as not migrating.
6Finish people, LFS, teams and Boards4 steps
  1. 1

    Reclaim mannequins

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed, signed in with an organisation owner's token

    Generate the mannequin CSV, add each person's GitHub username, and reclaim in bulk. Each person gets an email invitation and attribution moves when they accept (organisations using Enterprise Managed Users can add --skip-invitation).

    gh ado2gh generate-mannequin-csv --github-org contoso --output mannequins.csv
    gh ado2gh reclaim-mannequin --github-org contoso --csv mannequins.csv
    You should see: People accept and their pull request history shows under their GitHub accounts.
  2. 2

    Push Git LFS objects

    Where: Terminal on your migration machine with Git LFS installed (https://git-lfs.com)

    For each repository that uses LFS, mirror-clone it from Azure Repos, fetch every LFS object and push them to GitHub.

    git clone --mirror https://dev.azure.com/contoso/Payments/_git/payments-api
    cd payments-api.git
    git lfs fetch --all
    git lfs push --all https://github.com/contoso/payments-api.git
    You should see: A fresh clone from GitHub downloads LFS files with their content.
  3. 3

    Fill the teams

    Where: GitHub > your organisation > Teams > TEAM-PROJECT-Maintainers and TEAM-PROJECT-Admins

    Add members to the teams the script created, or link them to Entra ID groups with team synchronisation. Create extra teams with Read or Write roles for contributors.

    You should see: Each developer has the same level of access they had in the team project.
  4. 4

    Check the Azure Boards connection

    Where: Azure DevOps (https://dev.azure.com/contoso) > the team project > Project settings > GitHub connections

    Confirm the migrated repositories are listed. To add one by hand select New connection, sign in with GitHub, choose the repositories and select Approve, Install, & Authorize for the Azure Boards app. One connection can hold up to 2,000 repositories. Ask developers to mention work items as AB#123 in commits and pull requests.

    You should see: A test pull request mentioning AB# with a work item ID shows a link on the work item in Azure Boards.
7Keep Azure Pipelines or convert to GitHub Actions4 steps
  1. 1

    Confirm rewired pipelines build from GitHub

    Where: Azure DevOps (https://dev.azure.com/contoso) > the team project > Pipelines > select each pipeline > Run pipeline

    If you used --all, the script pointed each pipeline at its new GitHub repository. Queue a run from the main branch and open a test pull request on GitHub.

    You should see: The pipeline runs from the GitHub repository, and the pull request on GitHub shows the pipeline check.
  2. 2

    Install and configure GitHub Actions Importer

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed, with Docker running

    Install the extension, update the container and run configure. Choose Azure DevOps and enter a GitHub classic token with workflow, and an Azure DevOps token with read access to Agent Pools, Build, Code, Release, Service Connections, Task Groups and Variable Groups. Actions Importer works with Azure DevOps Services or Azure DevOps Server 2019 and later.

    gh extension install github/gh-actions-importer
    gh actions-importer update
    gh actions-importer configure
    You should see: gh actions-importer -h lists audit, forecast, dry-run and migrate.
  3. 3

    Audit, forecast and dry-run

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Audit the organisation to see what converts, forecast runner usage, then dry-run each pipeline by its ID (from the pipeline URL). Use release instead of pipeline for classic release pipelines. Secrets, service connections, unknown tasks, self-hosted agents, environments and approvals need manual work.

    gh actions-importer audit azure-devops --output-dir tmp/audit
    gh actions-importer forecast azure-devops --output-dir tmp/forecast_reports
    gh actions-importer dry-run azure-devops pipeline --pipeline-id 42 --output-dir tmp/dry-run
    You should see: tmp/audit holds the audit summary and tmp/dry-run holds the converted workflow for review.
  4. 4

    Open the workflow pull request

    Where: Terminal or PowerShell on your migration machine, with the GitHub CLI and the ADO2GH extension installed

    Run migrate for each pipeline. It opens a pull request with the workflow in the GitHub repository. Add secrets under the repository's Settings > Secrets and variables > Actions, fix the manual items, and merge when a run passes. Then disable the Azure pipeline so it does not run twice.

    gh actions-importer migrate azure-devops pipeline --pipeline-id 42 --target-url https://github.com/contoso/payments-api --output-dir tmp/migrate
    You should see: The workflow runs green in the repository's Actions tab and the old pipeline is disabled.
8Switch developers and disable Azure Repos2 steps
  1. 1

    Point local clones at GitHub

    Where: Each developer's terminal, inside their existing clone

    Change the remote rather than recloning, and update IDE connections and scripts that clone from Azure Repos.

    git remote set-url origin https://github.com/contoso/payments-api.git
    git fetch origin
    git remote -v
    You should see: git remote -v shows the github.com URL and git fetch succeeds.
  2. 2

    Disable the old repositories

    Where: Azure DevOps (https://dev.azure.com/contoso) > the team project > Project settings > Repos > Repositories > select the repository

    Once pipelines build from GitHub, disable each migrated repository. It stays listed, shows a disabled message and cannot be read or updated. Users with the Delete repository permission can re-enable it.

    You should see: Opening the repository in Azure Repos shows that it is disabled.
Worth knowing

Expert notes

The detail behind the plan: options compared, and what experienced engineers check first. Tap any item.

Compare all the ways to do this3 options

GEI for repos, keep Boards and Pipelines

Medium effort

Best for: Most organisations: move code and pull requests now, keep planning and CI stable

  • generate-script --all rewires pipelines and connects Boards
  • Smallest change for developers on day one
  • Two platforms to run and licence for a while
  • Work items stay in Azure Boards
Read more →

GEI plus GitHub Actions Importer

High effort

Best for: Teams that want to leave Azure Pipelines as part of the move

  • Ends up with code and CI on GitHub
  • Audit and forecast show the work before you commit
  • Secrets, service connections, agents, environments and approvals need manual work
  • Longer per-team cutover

Mirror push (code only)

Low effort

Best for: A few repositories, Azure DevOps Server sources, or a GitHub Enterprise Server destination

  • Works from any Git host to any GitHub product
  • Keeps full history, branches and tags
  • No pull requests, policies or work item links
  • Teams, pipelines and LFS still need follow-up
Read more →
Our advice

Use GEI with generate-script --all and keep Azure Boards and Pipelines connected for the first waves; convert pipelines to Actions team by team once the repositories have settled. Use a mirror push only when GEI cannot reach your source or destination, such as Azure DevOps Server or GitHub Enterprise Server.

Size waves by pull requests, not repository countSaves time

GitHub says migration time is largely based on the number of pull requests in a repository. A small repository with years of pull requests can take longer than a large one with few.

What to do

Run gh ado2gh inventory-report (Step 1) and sort repos.csv by pull request count. Put the busiest repositories in their own trial and give them the longest freeze window.

GitHub Docs: Prepare for your migration from Azure DevOps to GitHub
Map one Azure DevOps organisation to one GitHub organisationCost lever

Azure DevOps nests organisation, team project and repository; GitHub has enterprise, organisation and repository. GitHub advises against creating a GitHub organisation per team project, which fragments permissions, policies and billing.

What to do

Map each Azure DevOps organisation to one GitHub organisation. Represent team projects with teams and a repository naming rule, for example a project prefix.

GitHub Docs: Prepare for your migration from Azure DevOps to GitHub
Halt work during the production run, there is no deltaReduces risk

GitHub says the importer does not support delta migrations: changes made during or after the migration must be moved by hand.

What to do

Agree a freeze window per wave, complete or abandon open pull requests you do not want copied, and lock or disable the source repositories for the window (Step 4).

GitHub Docs: Migrate your repositories from Azure DevOps to GitHub
Recreate permissions as teamsUser impact

GEI does not migrate repository permissions from Azure DevOps. The ADO2GH CLI creates two teams per team project (TEAM-PROJECT-Maintainers and TEAM-PROJECT-Admins) but leaves their membership to you.

What to do

Export who has access to each team project, add members to the created teams on GitHub, or link the teams to Entra ID groups if your enterprise uses team synchronisation. Add Read and Write teams for people who were contributors.

GitHub Docs: Prepare for your migration from Azure DevOps to GitHub
Keep Boards and Pipelines for now, convert CI laterSaves time

Work items and pipelines are not migrated, but GitHub says both Azure Boards and Azure Pipelines can be fully integrated with GitHub. Converting every pipeline before the repository move turns one project into two.

What to do

Use generate-script --all so the script rewires pipelines to the GitHub repositories and sets up Boards integration. Convert pipelines to GitHub Actions per team afterwards with GitHub Actions Importer.

Tools that can get around this

GEI does not migrate pipelines. GitHub Actions Importer converts Azure Pipelines into GitHub Actions workflows and opens a pull request with them, listing the manual steps left.

  • GitHub Actions ImporterSecrets, service connections, unknown tasks and some triggers and gates still need manual work.
Third-party tools, not endorsed by the platform vendor. Confirm in the tool's documentation and run a pilot first.
GitHub Docs: Understand migrations from Azure DevOps to GitHub
Push Git LFS objects separatelyReduces risk

GEI migrates repositories that use Git LFS, but not the LFS objects. Builds that need those files fail after the move.

What to do

List repositories with filter=lfs in .gitattributes and push their LFS objects after migration with the commands in Step 5.

GitHub Docs: Understand migrations from Azure DevOps to GitHub
Check size limits and rulesets before the trialSaves time

GEI is limited to 40 GiB per repository (public preview) and 400 MiB per file during migration; GitHub limits files to 100 MiB after migration, commits and pushes to 2 GiB and refs to 255 bytes. Organisation rulesets such as commit author email rules can make migrations fail.

What to do

Run git-sizer (https://github.com/github/git-sizer) on the largest repositories. Migrate into an organisation where blocking rulesets do not apply to the new repositories, and turn them on afterwards.

Tools that can get around this

Rewriting history to move large files into Git LFS, or remove them, brings a repository under GEI's 400 MiB per-file limit before the trial.

  • Git LFS (git lfs migrate)GitHub documents git lfs migrate to move large files already in history into Git LFS, or git filter-repo to remove them. Both rewrite history, so every clone must be re-cloned.
Third-party tools, not endorsed by the platform vendor. Confirm in the tool's documentation and run a pilot first.
GitHub Docs: Understand migrations from Azure DevOps to GitHub
Use classic tokens and plan around IP restrictionsReduces risk

GEI accepts only GitHub personal access tokens (classic), and does not work if the organisation restricts classic tokens. An identity provider IP allow list such as an Entra Conditional Access policy can block the importer.

What to do

Allow classic tokens for the migration period, add GitHub's GEI ranges to any GitHub IP allow list, and agree with your identity team how to handle IdP allow lists during the migration, as GitHub describes.

GitHub Docs: Manage access for an Azure DevOps migration
Size the pipeline conversion with an audit firstCost lever

Actions Importer converts much of Azure Pipelines YAML and classic task groups, but secrets, service connections, unknown tasks, self-hosted agents, environments and approvals need manual work, and some triggers and gates are unsupported.

What to do

Run gh actions-importer audit azure-devops and forecast before you commit to dates. Convert simple pipelines first and leave release pipelines with gates and approvals until last.

GitHub Docs: Migrating from Azure DevOps with GitHub Actions Importer
Finish safely

Check it worked, or roll back

Full validation checklist8 checks

Run these before you declare the migration complete. Record the results in the data validation report.

CheckHow to checkWhat good looks like
Branches, tags and commits match

In fresh clones from Azure Repos and GitHub run git branch -r | wc -l, git tag | wc -l and git rev-list --all --count.

Counts are identical and the default branch head commit hash matches.
Pull requests arrived

Compare the pull request count per repository in repos.csv with gh pr list --state all --limit 5000 | wc -l in each GitHub repository. Open a sample to check comments and linked work items.

Counts match and sampled pull requests keep comments, reviewers and work item links.
Migration logs are clean

Read the downloaded logs and the Migration Log issue in each repository.

No errors; warnings only for branch policies and items you expected not to migrate.
Branch protection is in place

Try a direct push to the default branch from a developer account, and check the repository's Settings > Branches and Rules pages.

The push is refused and required reviews match the Azure DevOps branch policies.
People and teams are right

Check the organisation's mannequins and each team's members and repository roles.

No unclaimed mannequins for current staff, and each team has the access in your mapping sheet.
Boards links work

Create a test commit with AB# and a work item ID in its message, push it to GitHub, then open the work item in Azure Boards.

The work item shows the GitHub commit in its Development section.
Pipelines or workflows build from GitHub

Queue each rewired Azure pipeline, or check the Actions tab for converted workflows, and open a test pull request.

Builds pass from the GitHub repository and pull requests show the check.
Azure Repos is no longer written to

Open each migrated repository in Azure Repos.

It shows as disabled (or read-only), with no pushes after the freeze started.
Rollback planIf it goes wrong

Roll back if

  • The production run fails for key repositories and cannot be rerun inside the freeze window
  • Rewired pipelines cannot build or deploy from GitHub and the release cannot wait
  • Developers cannot sign in to GitHub (SSO, tokens or managed users) on the switch day
  • Pull request history or branch policies are missing in a way the owners will not accept

Point of no return

GEI copies from Azure Repos and leaves the repositories in place, so while developers have not pushed to GitHub you can re-enable Azure Repos and carry on. After people commit and merge on GitHub, that work exists only there and must be pushed back by hand. Deleting Azure Repos repositories, Azure pipelines (and their run history) or the Azure DevOps organisation cannot be undone from GitHub.

  1. 1

    Re-enable the source repositories

    Where: Azure DevOps (https://dev.azure.com/contoso) > the team project > Project settings > Repos > Repositories > select the repository

    Re-enable each disabled repository (needs the Delete repository permission) and restore contributors' push rights.

    You should see: Developers can push to Azure Repos again.
  2. 2

    Push back any commits made on GitHub

    Where: Terminal on your migration machine

    Mirror-clone each GitHub repository where work happened and push its branches and tags to Azure Repos. Recreate any pull requests opened on GitHub.

    git clone --mirror https://github.com/contoso/payments-api.git
    cd payments-api.git
    git push --all https://dev.azure.com/contoso/Payments/_git/payments-api
    git push --tags https://dev.azure.com/contoso/Payments/_git/payments-api
    You should see: Azure Repos shows every branch and tag created on GitHub.
  3. 3

    Point pipelines back at Azure Repos

    Where: Azure DevOps (https://dev.azure.com/contoso) > Pipelines > select the pipeline > Edit > Triggers > YAML > Get sources

    Change each rewired pipeline's source back to the Azure Repos repository and branch, save, and queue a run. Disable any GitHub Actions workflow that would also deploy.

    You should see: The pipeline runs from Azure Repos and nothing deploys twice.
  4. 4

    Archive the GitHub copies

    Where: GitHub > each repository > Settings > Danger Zone > Archive this repository

    Archive the migrated repositories so they become read-only, or delete them if you will rerun the migration with the same names.

    You should see: The repositories show as archived.
  5. 5

    Revert developer remotes

    Where: Each developer's clone

    Run git remote set-url origin with the Azure Repos URL and fetch.

    You should see: git remote -v shows dev.azure.com and git fetch succeeds.

Agree the triggers and who decides before the go / no-go meeting. Use the rollback plan template.

Common problems and fixes7 errors
You can only use GitHub Enterprise Importer to migrate from Azure DevOps Cloud

Cause: The source is Azure DevOps Server.

Fix: Migrate the collection to Azure DevOps Services first, or use a mirror push for code only.

pack exceeds maximum allowed size

Cause: A single commit or push is over GitHub's 2 GiB limit.

Fix: Remove or split the oversized commit in the source, then migrate again.

Token rejected or 401 from GitHub

Cause: A fine-grained token was used, a scope is missing, SSO is not authorised, or the organisation restricts classic tokens.

Fix: Use a classic token with the documented scopes, authorise it for SSO and allow classic tokens for the migration.

Migration fails on an organisation ruleset

Cause: An organisation ruleset such as a commit author email rule rejects old commits.

Fix: Exclude the new repositories from the ruleset during migration and apply it afterwards.

inventory-report fails or returns partial data

Cause: The Azure DevOps token lacks scopes or access to all organisations.

Fix: Create the ADO token with full access, as GitHub recommends for inventory-report, and allow all accessible organisations.

Builds fail after migration: LFS files are pointers

Cause: LFS objects are not migrated.

Fix: Run git lfs fetch --all from Azure Repos and git lfs push --all to GitHub.

AB# mentions link to the wrong work item

Cause: The GitHub repository is connected to projects in more than one Azure DevOps organisation; work item IDs are not unique across organisations.

Fix: Connect each GitHub repository to one Azure DevOps organisation only.

Search the full error library →

FAQ

Frequently asked questions

Does GitHub Enterprise Importer migrate Azure Boards work items?

No. It migrates Git history, pull requests and work item links on pull requests. Keep work items in Azure Boards and connect Boards to GitHub.

Can I migrate from Azure DevOps Server?

Not with GEI. Move to Azure DevOps Services first. GitHub Actions Importer does support Azure DevOps Server 2019 and later for pipeline conversion.

Will my Azure Pipelines keep working?

Yes, if you rewire them to the GitHub repositories. generate-script --all adds the rewiring to the migration script.

How do I convert Azure Pipelines to GitHub Actions?

Use GitHub Actions Importer: audit, forecast, dry-run and migrate. It opens a pull request with the converted workflow, and you finish the items it marks as manual.

Are repository permissions migrated?

No. The CLI creates Maintainers and Admins teams per team project; you add the members or link them to identity provider groups.

Can I sync changes after the first migration?

No. GEI has no delta migration, so halt work in each repository during its production run.

Sources

Official sources

11 official pages this guide is based on