├── .devcontainer
└── devcontainer.json
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── 01_epic.md
│ ├── 02_batch.md
│ ├── 03_task.md
│ ├── 04_bug.md
│ └── 05_featurerequest.md
├── codeql
│ ├── codeql-config.yml
│ └── csharp-custom-queries.qls
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── CI.yml
│ ├── integration-tests.yml
│ └── publish-test-results.yml
├── .gitignore
├── .projections.json
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LATEST-VERSION.txt
├── LICENSE
├── README.md
├── RELEASENOTES.md
├── ThirdPartyNotices.txt
├── docs
└── ContributingCode.md
├── global.json
├── images
└── CodeLayers.png
├── publish.ps1
├── releasenotes
├── v0.1.md
├── v0.10.md
├── v0.11.md
├── v0.12.md
├── v0.13.md
├── v0.14.md
├── v0.15.md
├── v0.16.md
├── v0.17.md
├── v0.18.md
├── v0.19.md
├── v0.2.md
├── v0.20.md
├── v0.21.md
├── v0.22.md
├── v0.23.md
├── v0.24.md
├── v0.25.md
├── v0.26.md
├── v0.27.md
├── v0.28.md
├── v0.29.md
├── v0.3.md
├── v0.30.md
├── v0.31.md
├── v0.32.md
├── v0.33.md
├── v0.34.md
├── v0.35.md
├── v0.36.md
├── v0.37.md
├── v0.38.md
├── v0.39.md
├── v0.4.md
├── v0.40.md
├── v0.41.md
├── v0.42.md
├── v0.43.md
├── v0.44.md
├── v0.45.md
├── v0.46.md
├── v0.47.md
├── v0.48.md
├── v0.49.md
├── v0.5.md
├── v0.6.1.md
├── v0.6.md
├── v0.7.md
├── v0.8.md
├── v0.9.md
├── v1.0.0.md
├── v1.1.0.md
├── v1.10.0.md
├── v1.10.1.md
├── v1.11.0.md
├── v1.12.0.md
├── v1.13.0.md
├── v1.14.0.md
├── v1.15.0.md
├── v1.15.1.md
├── v1.2.0.md
├── v1.2.1.md
├── v1.3.0.md
├── v1.4.0.md
├── v1.5.0.md
├── v1.6.0.md
├── v1.7.0.md
├── v1.7.1.md
├── v1.8.0.md
├── v1.9.0.md
└── v1.9.1.md
└── src
├── .editorconfig
├── .idea
└── .idea.OctoshiftCLI
│ └── .idea
│ ├── .gitignore
│ ├── .name
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── Octoshift
├── ArchiveMigrationStatus.cs
├── CliContext.cs
├── CodeScanningAlertState.cs
├── Commands
│ ├── AbortMigration
│ │ ├── AbortMigrationCommandArgs.cs
│ │ ├── AbortMigrationCommandBase.cs
│ │ └── AbortMigrationCommandHandler.cs
│ ├── CommandArgs.cs
│ ├── CommandBase.cs
│ ├── CreateTeam
│ │ ├── CreateTeamCommandArgs.cs
│ │ ├── CreateTeamCommandBase.cs
│ │ └── CreateTeamCommandHandler.cs
│ ├── DownloadLogs
│ │ ├── DownloadLogsCommandArgs.cs
│ │ ├── DownloadLogsCommandBase.cs
│ │ └── DownloadLogsCommandHandler.cs
│ ├── GenerateMannequinCsv
│ │ ├── GenerateMannequinCsvCommandArgs.cs
│ │ ├── GenerateMannequinCsvCommandBase.cs
│ │ └── GenerateMannequinCsvCommandHandler.cs
│ ├── GrantMigratorRole
│ │ ├── GrantMigratorRoleCommandArgs.cs
│ │ ├── GrantMigratorRoleCommandBase.cs
│ │ └── GrantMigratorRoleCommandHandler.cs
│ ├── ICommandHandler.cs
│ ├── ReclaimMannequin
│ │ ├── ReclaimMannequinCommandArgs.cs
│ │ ├── ReclaimMannequinCommandBase.cs
│ │ └── ReclaimMannequinCommandHandler.cs
│ ├── RevokeMigratorRole
│ │ ├── RevokeMigratorRoleCommandArgs.cs
│ │ ├── RevokeMigratorRoleCommandBase.cs
│ │ └── RevokeMigratorRoleCommandHandler.cs
│ ├── SecretAttribute.cs
│ └── WaitForMigration
│ │ ├── WaitForMigrationCommandArgs.cs
│ │ ├── WaitForMigrationCommandBase.cs
│ │ └── WaitForMigrationCommandHandler.cs
├── Contracts
│ ├── ISourceGithubApiFactory.cs
│ ├── ITargetGithubApiFactory.cs
│ └── IVersionProvider.cs
├── Extensions
│ ├── AssemblyExtensions.cs
│ ├── CommandExtensions.cs
│ ├── CommandLineOptionExtensions.cs
│ ├── EnumerableExtensions.cs
│ ├── HttpRequestMessageExtensions.cs
│ ├── NumericExtensions.cs
│ ├── ObjectExtensions.cs
│ ├── PropertyInfoExtensions.cs
│ └── StringExtensions.cs
├── Factories
│ └── HttpDownloadServiceFactory.cs
├── InsufficientPermissionsMessageGenerator.cs
├── Models
│ ├── AdoRepository.cs
│ ├── BbsRepository.cs
│ ├── CodeScanningAlert.cs
│ ├── CodeScanningAlertInstance.cs
│ ├── CodeScanningAnalysis.cs
│ ├── CreateAttributionInvitationResult.cs
│ ├── GithubSecretScanningAlert.cs
│ ├── GraphqlResult.cs
│ ├── Mannequin.cs
│ ├── MannequinReclaimResult.cs
│ └── SarifProcessingStatus.cs
├── Octoshift.csproj
├── OctoshiftCliException.cs
├── OrganizationMigrationStatus.cs
├── RepositoryMigrationStatus.cs
├── RetryPolicy.cs
├── SecretScanningAlert.cs
└── Services
│ ├── AdoApi.cs
│ ├── AdoClient.cs
│ ├── ArchiveUploader.cs
│ ├── AwsApi.cs
│ ├── AzureApi.cs
│ ├── BasicHttpClient.cs
│ ├── BbsApi.cs
│ ├── BbsClient.cs
│ ├── CodeScanningAlertService.cs
│ ├── ConfirmationService.cs
│ ├── DateTimeProvider.cs
│ ├── EnvironmentVariableProvider.cs
│ ├── FileSystemProvider.cs
│ ├── GenericArgsBinder.cs
│ ├── GithubApi.cs
│ ├── GithubClient.cs
│ ├── GithubStatusApi.cs
│ ├── HttpDownloadService.cs
│ ├── OctoLogger.cs
│ ├── ReclaimService.cs
│ ├── SecretScanningAlertService.cs
│ ├── StringCompressor.cs
│ ├── VersionChecker.cs
│ └── WarningsCountLogger.cs
├── OctoshiftCLI.IntegrationTests
├── .editorconfig
├── AdoBasicToGithub.cs
├── AdoCsvToGithub.cs
├── AdoServerToGithub.cs
├── AdoToGithub.cs
├── BbsToGithub.cs
├── GhesToGithub.cs
├── GithubToGithub.cs
├── OctoshiftCLI.IntegrationTests.csproj
├── TestHelper.cs
└── xunit.runner.json
├── OctoshiftCLI.Tests
├── .editorconfig
├── InsufficientPermissionsMessageGeneratorTest.cs
├── Octoshift
│ ├── Commands
│ │ ├── AbortMigration
│ │ │ ├── AbortMigrationCommandBaseTests.cs
│ │ │ └── AbortMigrationCommandHandlerTests.cs
│ │ ├── CommandArgsTests.cs
│ │ ├── CreateTeam
│ │ │ ├── CreateTeamCommandBaseTests.cs
│ │ │ └── CreateTeamCommandHandlerTests.cs
│ │ ├── DownloadLogs
│ │ │ └── DownloadLogsCommandHandlerTests.cs
│ │ ├── GenerateMannequinCsv
│ │ │ ├── GenerateMannequinCsvCommandBaseTests.cs
│ │ │ └── GenerateMannequinCsvCommandHandlerTests.cs
│ │ ├── GrantMigratorRole
│ │ │ ├── GrantMigratorRoleCommandArgsTests.cs
│ │ │ ├── GrantMigratorRoleCommandBaseTests.cs
│ │ │ └── GrantMigratorRoleCommandHandlerTests.cs
│ │ ├── ReclaimMannequin
│ │ │ ├── ReclaimMannequinCommandArgsTests.cs
│ │ │ ├── ReclaimMannequinCommandBaseTests.cs
│ │ │ └── ReclaimMannequinCommandHandlerTests.cs
│ │ ├── RevokeMigratorRole
│ │ │ ├── RevokeMigratorRoleCommandArgsTests.cs
│ │ │ ├── RevokeMigratorRoleCommandBaseTests.cs
│ │ │ └── RevokeMigratorRoleCommandHandlerTests.cs
│ │ └── WaitForMigration
│ │ │ ├── WaitForMigrationCommandArgsTests.cs
│ │ │ ├── WaitForMigrationCommandBaseTests.cs
│ │ │ └── WaitForMigrationCommandHandlerTests.cs
│ ├── Factories
│ │ └── HttpDownloadServiceFactoryTests.cs
│ └── Services
│ │ ├── AdoApiTests.cs
│ │ ├── AdoClientTests.cs
│ │ ├── ArchiveUploadersTests.cs
│ │ ├── AwsApiTests.cs
│ │ ├── AzureApiTests.cs
│ │ ├── BasicHttpClientTests.cs
│ │ ├── BbsApiTests.cs
│ │ ├── BbsClientTests.cs
│ │ ├── CodeScanningAlertServiceTests.cs
│ │ ├── ConfirmationServiceTests.cs
│ │ ├── EnvironmentVariableProviderTests.cs
│ │ ├── GithubApiTests.cs
│ │ ├── GithubClientTests.cs
│ │ ├── HttpDownloadServiceTests.cs
│ │ ├── OctoLoggerTests.cs
│ │ ├── ReclaimServiceTests.cs
│ │ ├── SecretScanningAlertServiceTests.cs
│ │ ├── StringCompressorTests.cs
│ │ ├── VersionCheckerTests.cs
│ │ └── WarningsCountLoggerTests.cs
├── OctoshiftCLI.Tests.csproj
├── RepositoryMigrationStatusTests.cs
├── StringExtensionsTests.cs
├── TestExtensionMethods.cs
├── TestHelpers.cs
├── ado2gh
│ ├── Commands
│ │ ├── AbortMigration
│ │ │ └── AbortMigrationCommandTests.cs
│ │ ├── AddTeamToRepo
│ │ │ ├── AddTeamToRepoCommandHandlerTests.cs
│ │ │ └── AddTeamToRepoCommandTests.cs
│ │ ├── ConfigureAutoLinkCommand
│ │ │ ├── ConfigureAutoLinkCommandHandlerTests.cs
│ │ │ └── ConfigureAutoLinkCommandTests.cs
│ │ ├── CreateTeam
│ │ │ └── CreateTeamCommandTests.cs
│ │ ├── DisableRepo
│ │ │ ├── DisableRepoCommandHandlerTests.cs
│ │ │ └── DisableRepoCommandTests.cs
│ │ ├── DownloadLogs
│ │ │ └── DownloadLogsCommandTests.cs
│ │ ├── GenerateMannequinCsv
│ │ │ └── GenerateMannequinCsvCommandTests.cs
│ │ ├── GenerateScript
│ │ │ ├── GenerateScriptCommandHandlerTests.cs
│ │ │ └── GenerateScriptCommandTests.cs
│ │ ├── GrantMigratorRole
│ │ │ └── GrantMigratorRoleCommandTests.cs
│ │ ├── IntegrateBoards
│ │ │ ├── IntegrateBoardsCommandHandlerTests.cs
│ │ │ └── IntegrateBoardsCommandTests.cs
│ │ ├── InventoryReport
│ │ │ ├── InventoryReportCommandHandlerTests.cs
│ │ │ └── InventoryReportCommandTests.cs
│ │ ├── LockRepo
│ │ │ ├── LockRepoCommandHandlerTests.cs
│ │ │ └── LockRepoCommandTests.cs
│ │ ├── MigrateRepo
│ │ │ ├── MigrateRepoCommandHandlerTests.cs
│ │ │ └── MigrateRepoCommandTests.cs
│ │ ├── ReclaimMannequin
│ │ │ └── ReclaimMannequinCommandTests.cs
│ │ ├── RevokeMigratorRole
│ │ │ └── RevokeMigratorRoleCommandTests.cs
│ │ ├── RewirePipeline
│ │ │ ├── RewirePipelineCommandHandlerTests.cs
│ │ │ └── RewirePipelineCommandTests.cs
│ │ ├── ShareServiceConnection
│ │ │ ├── ShareServiceConnectionCommandHandlerTests.cs
│ │ │ └── ShareServiceConnectionCommandTests.cs
│ │ └── WaitForMigration
│ │ │ └── WaitForMigrationCommandTests.cs
│ ├── Factories
│ │ ├── AdoApiFactoryTests.cs
│ │ └── GithubApiFactoryTests.cs
│ └── Services
│ │ ├── AdoInspectorServiceTests.cs
│ │ ├── OrgsCsvGeneratorServiceTests.cs
│ │ ├── PipelinesCsvGeneratorServiceTests.cs
│ │ ├── ReposCsvGeneratorServiceTests.cs
│ │ └── TeamProjectsCsvGeneratorServiceTests.cs
├── bbs2gh
│ ├── Commands
│ │ ├── AbortMigration
│ │ │ └── AbortMigrationCommandTests.cs
│ │ ├── CreateTeam
│ │ │ └── CreateTeamCommandTests.cs
│ │ ├── DownloadLogs
│ │ │ └── DownloadLogsCommandTests.cs
│ │ ├── GenerateMannequinCsv
│ │ │ └── GenerateMannequinCsvCommandTests.cs
│ │ ├── GenerateScript
│ │ │ ├── GenerateScriptCommandArgsTests.cs
│ │ │ ├── GenerateScriptCommandHandlerTests.cs
│ │ │ └── GenerateScriptCommandTests.cs
│ │ ├── GrantMigratorRole
│ │ │ └── GrantMigratorRoleCommandTests.cs
│ │ ├── InventoryReport
│ │ │ ├── InventoryReportCommandHandlerTests.cs
│ │ │ └── InventoryReportCommandTests.cs
│ │ ├── MigrateRepo
│ │ │ ├── MigrateRepoCommandArgsTests.cs
│ │ │ ├── MigrateRepoCommandHandlerTests.cs
│ │ │ └── MigrateRepoCommandTests.cs
│ │ ├── ReclaimMannequin
│ │ │ └── ReclaimMannequinCommandTests.cs
│ │ ├── RevokeMigratorRole
│ │ │ └── RevokeMigratorRoleCommandTests.cs
│ │ └── WaitForMigration
│ │ │ └── WaitForMigrationCommandTests.cs
│ ├── Factories
│ │ ├── AwsApiFactoryTests.cs
│ │ └── BbsApiFactoryTests.cs
│ └── Services
│ │ ├── BbsInspectorServiceTests.cs
│ │ ├── BbsSmbArchiveDownloaderTests.cs
│ │ ├── BbsSshArchiveDownloaderTests.cs
│ │ ├── ProjectsCsvGeneratorServiceTests.cs
│ │ └── ReposCsvGeneratorServiceTests.cs
└── gei
│ ├── Commands
│ ├── AbortMigration
│ │ └── AbortMigrationCommandTests.cs
│ ├── CreateTeam
│ │ └── CreateTeamCommandTests.cs
│ ├── DownloadLogs
│ │ └── DownloadLogsCommandTests.cs
│ ├── GenerateMannequinCsv
│ │ └── GenerateMannequinCsvCommandTests.cs
│ ├── GenerateScript
│ │ ├── GenerateScriptCommandArgsTests.cs
│ │ ├── GenerateScriptCommandHandlerTests.cs
│ │ └── GenerateScriptCommandTests.cs
│ ├── GrantMigratorRole
│ │ └── GrantMigratorRoleCommandTests.cs
│ ├── MigrateCodeScanningAlerts
│ │ ├── MigrateCodeScanningAlertsCommandArgsTests.cs
│ │ └── MigrateCodeScanningAlertsCommandTests.cs
│ ├── MigrateOrg
│ │ ├── MigrateOrgCommandArgsTests.cs
│ │ ├── MigrateOrgCommandHandlerTests.cs
│ │ └── MigrateOrgCommandTests.cs
│ ├── MigrateRepo
│ │ ├── MigrateRepoCommandArgsTests.cs
│ │ ├── MigrateRepoCommandHandlerTests.cs
│ │ └── MigrateRepoCommandTests.cs
│ ├── MigrateSecretAlerts
│ │ ├── MigrateSecretAlertsCommandArgsTests.cs
│ │ └── MigrateSecretAlertsCommandTests.cs
│ ├── ReclaimMannequin
│ │ └── ReclaimMannequinCommandTests.cs
│ ├── RevokeMigratorRole
│ │ └── RevokeMigratorRoleCommandTests.cs
│ └── WaitForMigration
│ │ └── WaitForMigrationCommandTests.cs
│ ├── Factories
│ ├── AwsApiFactoryTests.cs
│ ├── AzureApiFactoryTests.cs
│ └── GithubApiFactoryTests.cs
│ └── Services
│ └── GhesVersionCheckerTests.cs
├── OctoshiftCLI.sln
├── ado2gh
├── Commands
│ ├── AbortMigration
│ │ └── AbortMigrationCommand.cs
│ ├── AddTeamToRepo
│ │ ├── AddTeamToRepoCommand.cs
│ │ ├── AddTeamToRepoCommandArgs.cs
│ │ └── AddTeamToRepoCommandHandler.cs
│ ├── ConfigureAutoLink
│ │ ├── ConfigureAutoLinkCommand.cs
│ │ ├── ConfigureAutoLinkCommandArgs.cs
│ │ └── ConfigureAutoLinkCommandHandler.cs
│ ├── CreateTeam
│ │ └── CreateTeamCommand.cs
│ ├── DisableRepo
│ │ ├── DisableRepoCommand.cs
│ │ ├── DisableRepoCommandArgs.cs
│ │ └── DisableRepoCommandHandler.cs
│ ├── DownloadLogs
│ │ └── DownloadLogsCommand.cs
│ ├── GenerateMannequinCsv
│ │ └── GenerateMannequinCsvCommand.cs
│ ├── GenerateScript
│ │ ├── GenerateScriptCommand.cs
│ │ ├── GenerateScriptCommandArgs.cs
│ │ └── GenerateScriptCommandHandler.cs
│ ├── GrantMigratorRole
│ │ └── GrantMigratorRoleCommand.cs
│ ├── IntegrateBoards
│ │ ├── IntegrateBoardsCommand.cs
│ │ ├── IntegrateBoardsCommandArgs.cs
│ │ └── IntegrateBoardsCommandHandler.cs
│ ├── InventoryReport
│ │ ├── InventoryReportCommand.cs
│ │ ├── InventoryReportCommandArgs.cs
│ │ └── InventoryReportCommandHandler.cs
│ ├── LockRepo
│ │ ├── LockRepoCommand.cs
│ │ ├── LockRepoCommandArgs.cs
│ │ └── LockRepoCommandHandler.cs
│ ├── MigrateRepo
│ │ ├── MigrateRepoCommand.cs
│ │ ├── MigrateRepoCommandArgs.cs
│ │ └── MigrateRepoCommandHandler.cs
│ ├── ReclaimMannequin
│ │ └── ReclaimMannequinCommand.cs
│ ├── RevokeMigratorRole
│ │ └── RevokeMigratorRoleCommand.cs
│ ├── RewirePipeline
│ │ ├── RewirePipelineCommand.cs
│ │ ├── RewirePipelineCommandArgs.cs
│ │ └── RewirePipelineCommandHandler.cs
│ ├── ShareServiceConnection
│ │ ├── ShareServiceConnectionCommand.cs
│ │ ├── ShareServiceConnectionCommandArgs.cs
│ │ └── ShareServiceConnectionCommandHandler.cs
│ └── WaitForMigration
│ │ └── WaitForMigrationCommand.cs
├── Factories
│ ├── AdoApiFactory.cs
│ ├── AdoInspectorServiceFactory.cs
│ └── GithubApiFactory.cs
├── Program.cs
├── Services
│ ├── AdoInspectorService.cs
│ ├── OrgsCsvGeneratorService.cs
│ ├── PipelinesCsvGeneratorService.cs
│ ├── ReposCsvGeneratorService.cs
│ └── TeamProjectsCsvGeneratorService.cs
└── ado2gh.csproj
├── bbs2gh
├── BbsSettings.cs
├── Commands
│ ├── AbortMigration
│ │ └── AbortMigrationCommand.cs
│ ├── CreateTeam
│ │ └── CreateTeamCommand.cs
│ ├── DownloadLogs
│ │ └── DownloadLogsCommand.cs
│ ├── GenerateMannequinCsv
│ │ └── GenerateMannequinCsvCommand.cs
│ ├── GenerateScript
│ │ ├── GenerateScriptCommand.cs
│ │ ├── GenerateScriptCommandArgs.cs
│ │ └── GenerateScriptCommandHandler.cs
│ ├── GrantMigratorRole
│ │ └── GrantMigratorRoleCommand.cs
│ ├── InventoryReport
│ │ ├── InventoryReportCommand.cs
│ │ ├── InventoryReportCommandArgs.cs
│ │ └── InventoryReportCommandHandler.cs
│ ├── MigrateRepo
│ │ ├── MigrateRepoCommand.cs
│ │ ├── MigrateRepoCommandArgs.cs
│ │ └── MigrateRepoCommandHandler.cs
│ ├── ReclaimMannequin
│ │ └── ReclaimMannequinCommand.cs
│ ├── RevokeMigratorRole
│ │ └── RevokeMigratorRoleCommand.cs
│ └── WaitForMigration
│ │ └── WaitForMigrationCommand.cs
├── ExportState.cs
├── Factories
│ ├── AwsApiFactory.cs
│ ├── AzureApiFactory.cs
│ ├── BbsApiFactory.cs
│ ├── BbsArchiveDownloaderFactory.cs
│ ├── BbsInspectorServiceFactory.cs
│ ├── BlobServiceClientFactory.cs
│ ├── GithubApiFactory.cs
│ ├── IAzureApiFactory.cs
│ └── IBlobServiceClientFactory.cs
├── Program.cs
├── Services
│ ├── BbsInspectorService.cs
│ ├── BbsSmbArchiveDownloader.cs
│ ├── BbsSshArchiveDownloader.cs
│ ├── IBbsArchiveDownloader.cs
│ ├── ProjectsCsvGeneratorService.cs
│ └── ReposCsvGeneratorService.cs
└── bbs2gh.csproj
└── gei
├── Commands
├── AbortMigration
│ └── AbortMigrationCommand.cs
├── CreateTeam
│ └── CreateTeamCommand.cs
├── DownloadLogs
│ └── DownloadLogsCommand.cs
├── GenerateMannequinCsv
│ └── GenerateMannequinCsvCommand.cs
├── GenerateScript
│ ├── GenerateScriptCommand.cs
│ ├── GenerateScriptCommandArgs.cs
│ └── GenerateScriptCommandHandler.cs
├── GrantMigratorRole
│ └── GrantMigratorRoleCommand.cs
├── MigrateCodeScanningAlerts
│ ├── MigrateCodeScanningAlertsCommand.cs
│ ├── MigrateCodeScanningAlertsCommandArgs.cs
│ └── MigrateCodeScanningAlertsCommandHandler.cs
├── MigrateOrg
│ ├── MigrateOrgCommand.cs
│ ├── MigrateOrgCommandArgs.cs
│ └── MigrateOrgCommandHandler.cs
├── MigrateRepo
│ ├── MigrateRepoCommand.cs
│ ├── MigrateRepoCommandArgs.cs
│ └── MigrateRepoCommandHandler.cs
├── MigrateSecretAlerts
│ ├── MigrateSecretAlertsCommand.cs
│ ├── MigrateSecretAlertsCommandArgs.cs
│ └── MigrateSecretAlertsCommandHandler.cs
├── ReclaimMannequin
│ └── ReclaimMannequinCommand.cs
├── RevokeMigratorRole
│ └── RevokeMigratorRoleCommand.cs
└── WaitForMigration
│ └── WaitForMigrationCommand.cs
├── Factories
├── AwsApiFactory.cs
├── AzureApiFactory.cs
├── BlobServiceClientFactory.cs
├── CodeScanningAlertServiceFactory.cs
├── GhesVersionCheckerFactory.cs
├── GithubApiFactory.cs
├── IAzureApiFactory.cs
├── IBlobServiceClientFactory.cs
└── SecretScanningAlertServiceFactory.cs
├── Program.cs
├── Services
└── GhesVersionChecker.cs
└── gei.csproj
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "C# (.NET)",
3 | "image": "mcr.microsoft.com/devcontainers/dotnet:8.0",
4 |
5 | // Configure tool-specific properties.
6 | "customizations": {
7 | // Configure properties specific to VS Code.
8 | "vscode": {
9 | // Add the IDs of extensions you want installed when the container is created.
10 | "extensions": [
11 | "ms-dotnettools.csdevkit",
12 | "github.copilot",
13 | "ms-vscode.powershell"
14 | ]
15 | }
16 | },
17 | "features": {
18 | "ghcr.io/devcontainers/features/github-cli:1": {}
19 | },
20 | "remoteUser": "vscode"
21 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/01_epic.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Epic
3 | about: Issue to track epics (https://thehub.github.com/engineering/how-we-work/#epic-workflow)
4 | title:
5 | labels: epic
6 | assignees:
7 | ---
8 |
11 | TBD
12 |
13 | ## Planning
14 |
15 | ### EPD ([more info](https://github.com/github/product-operations/blob/main/planning-process/planning-terms.md#defining-our-planning-terms))
16 | **E**ngineering:
17 | **P**roduct:
18 | **D**esign:
19 | Other Stakeholders:
20 |
21 | ### Initiative / OKR Mapping
22 |
23 | TBD
24 |
25 | ### People Plan
26 |
27 | TBD
28 |
29 | ## Batches
30 |
33 | - [ ] ...
34 |
35 | ### Dependencies
36 |
37 |
40 | TBD
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/02_batch.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Batch
3 | about: Issue to track batches (https://thehub.github.com/engineering/how-we-work/#batch-workflow)
4 | title:
5 | labels: batch
6 | assignees:
7 | ---
8 |
11 | TBD
12 |
13 | ## Tasks
14 |
17 | - [ ] ...
18 |
19 | ## Dependencies
20 |
23 | - ...
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/03_task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Task
3 | about: Issue to track tasks (https://thehub.github.com/engineering/how-we-work/#task-workflow)
4 | title:
5 | labels: task
6 | assignees:
7 | ---
8 |
11 | TBD
12 |
13 | ## Todo
14 |
17 | - [ ] ...
18 |
19 | ## Dependencies
20 |
23 | - ...
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/04_bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Report a Bug
3 | about: Found something wrong? Report it here.
4 | title:
5 | labels: bug
6 | assignees:
7 | ---
8 |
9 | 🚨 Please Read Before Posting 🚨
10 |
11 | You are currently in a PUBLIC REPOSITORY.
12 |
13 | No Internal Information: Do not share links to internal Zendesk tickets, repositories, team names, or any internal
14 | details.
15 |
16 | Confidentiality Matters: Remember that this repository is visible to the public. Avoid posting anything that should
17 | remain confidential or private.
18 |
19 | ## Description
20 |
21 |
22 |
23 | ## Reproduction Steps
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/05_featurerequest.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Request a Feature
3 | about: Have an idea how to make this better? Let us know
4 | title:
5 | labels: feature-request
6 | assignees:
7 | ---
8 |
9 | ## Description
10 |
11 |
--------------------------------------------------------------------------------
/.github/codeql/codeql-config.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL code scanning custom configuration"
2 | disable-default-queries: true
3 | queries:
4 | - name: Use the custom query suite file from this repo
5 | uses: ./.github/codeql/csharp-custom-queries.qls
--------------------------------------------------------------------------------
/.github/codeql/csharp-custom-queries.qls:
--------------------------------------------------------------------------------
1 | - import: codeql-suites/csharp-security-and-quality.qls
2 | from: codeql-csharp
3 | - exclude:
4 | id: cs/simplifiable-boolean-expression
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for more information:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 | # https://containers.dev/guide/dependabot
6 |
7 | version: 2
8 | updates:
9 | - package-ecosystem: "nuget" # See documentation for possible values
10 | directory: "/" # Location of package manifests
11 | schedule:
12 | interval: "weekly"
13 | - package-ecosystem: "github-actions" # See documentation for possible values
14 | directory: "/" # Location of package manifests
15 | schedule:
16 | interval: "weekly"
17 | - package-ecosystem: "devcontainers"
18 | directory: "/"
19 | schedule:
20 | interval: weekly
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | - [ ] Did you write/update appropriate tests
6 | - [ ] Release notes updated (if appropriate)
7 | - [ ] Appropriate logging output
8 | - [ ] Issue linked
9 | - [ ] Docs updated (or issue created)
10 | - [ ] New package licenses are added to `ThirdPartyNotices.txt` (if applicable)
11 |
12 |
--------------------------------------------------------------------------------
/.projections.json:
--------------------------------------------------------------------------------
1 | {
2 | "src/Octoshift/*.cs": {
3 | "alternate": "src/OctoshiftCLI.Tests/{}Tests.cs"
4 | },
5 |
6 | "src/OctoshiftCLI.Tests/*Tests.cs": {
7 | "alternate": "src/Octoshift/{}.cs"
8 | },
9 |
10 | "src/ado2gh/*.cs": {
11 | "alternate": "src/OctoshiftCLI.Tests/ado2gh/{}Tests.cs"
12 | },
13 |
14 | "src/OctoshiftCLI.Tests/ado2gh/*Tests.cs": {
15 | "alternate": "src/ado2gh/{}.cs"
16 | },
17 |
18 | "src/bbs2gh/*.cs": {
19 | "alternate": "src/OctoshiftCLI.Tests/bbs2gh/{}Tests.cs"
20 | },
21 |
22 | "src/OctoshiftCLI.Tests/bbs2gh/*Tests.cs": {
23 | "alternate": "src/bbs2gh/{}.cs"
24 | },
25 |
26 | "src/gei/*.cs": {
27 | "alternate": "src/OctoshiftCLI.Tests/gei/{}Tests.cs"
28 | },
29 |
30 | "src/OctoshiftCLI.Tests/gei/*Tests.cs": {
31 | "alternate": "src/gei/{}.cs"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet-test-explorer.testProjectPath": "**/OctoshiftCLI.Tests.csproj"
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/src/gei/gei.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "build-ado2gh",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "build",
22 | "${workspaceFolder}/src/ado2gh/ado2gh.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "test",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "test",
34 | "${workspaceFolder}/src/OctoshiftCLI.Tests",
35 | "/property:GenerateFullPaths=true",
36 | "/consoleloggerparameters:NoSummary"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | },
40 | {
41 | "label": "watch",
42 | "command": "dotnet",
43 | "type": "process",
44 | "args": [
45 | "watch",
46 | "run",
47 | "--project",
48 | "${workspaceFolder}/src/gei/gei.csproj"
49 | ],
50 | "problemMatcher": "$msCompile"
51 | }
52 | ]
53 | }
--------------------------------------------------------------------------------
/LATEST-VERSION.txt:
--------------------------------------------------------------------------------
1 | v1.15.1
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 GitHub
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/RELEASENOTES.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.404",
4 | "rollForward": "minor"
5 | }
6 | }
--------------------------------------------------------------------------------
/images/CodeLayers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/github/gh-gei/f9ee1915c58b9c89c11f0296de8a2c4f394869ad/images/CodeLayers.png
--------------------------------------------------------------------------------
/releasenotes/v0.1.md:
--------------------------------------------------------------------------------
1 | first test release
--------------------------------------------------------------------------------
/releasenotes/v0.10.md:
--------------------------------------------------------------------------------
1 | - Add `wait-for-migration` command. It waits for the provided migration and returns as soon as it is complete.
2 | - Add `--wait` option to `migrate-repo` command. If set to `true` (default is `false`) it will synchronously wait for the migration to finish, otherwise it will just queue up a repo migration and return the `migration-id`.
3 | - Support parallel migrations with `generate-script` for both `ado2gh` and `gh gei`. `generate-script` now by default generates a script to perform migrations in parallel. Adding `--sequential` flag will force migrations to perform in a sequential (one by one) fashion.
4 | - Deprecate `--ssh` flag in `generate-script` and `migrate-repo` commands for both `ado2gh` and `gh gei`.
5 | - Add powershell shebang command for execution of script on unix based systems.
6 | - Add executable bit on generated scripts in unix based systems.
7 | - Updates most commands to be idempotent. They will check if there is anything to do, and if not they will print a message to that effect and complete successfully. E.g. create-team will check if the team already exists and if so exit as success (compared to previously where it would crash). The following commands have been updated:
8 | - configure-autolink
9 | - create-team
10 |
--------------------------------------------------------------------------------
/releasenotes/v0.11.md:
--------------------------------------------------------------------------------
1 | - Updating commands to be idempotent. They will check if there is anything to do, and if not they will print a message to that effect and complete successfully. E.g. create-team will check if the team already exists and if so exit as success (compared to previously where it would crash). The following commands have been updated:
2 | - disable-ado-repo
3 | - The change to automatically set the +x bit on the generated migration script has been rolled back (introduced in v0.10). We discovered a bug with this that caused `generate-script` to crash on MacOS. We're temporarily rolling this back to unblock customers while we investigate and fix the problem.
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.12.md:
--------------------------------------------------------------------------------
1 | - Add ado-team-project parameter to `ado2gh generate-script` command
2 | - Add ado-team-project parameter to `gh gei generate-script` command
3 | - Publishing ARM64 binaries for those users on M1 macs
4 | - All commands in both `ado2gh` and `gei` now optionally accept required PATs as args as an alternate way to setting them as env variables.
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.13.md:
--------------------------------------------------------------------------------
1 | - Compare the current running version against the latest version available on github.com and print out a message letting you know if you are up to date or not
2 | - Add `reclaim-mannequin` command. Reclaims a mannequin, by sending a mannequin attribution invitation to the target user. If the mannequin has been previously mapped (and accepted) it will refuse to do so unless the `--force` flag is set.
3 | - Sometimes `wait-for-migration` would error with a 502 error, now it will retry automatically when this happens
4 | - Sometimes `create-team` would error with a 404 error, now it will retry automatically when this happens
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.14.md:
--------------------------------------------------------------------------------
1 | - Significant overhaul of how the `generate-script` command args work. Now by default it will generate a minimal script that only migrates the repos, and you will need to pass additional flags to script out additional automation (e.g. `--rewire-pipelines`, `--create-teams`, etc). The `--all` flag will include all the automation in the script (the same as the previous version with no flags).
2 | - Updates most commands to be idempotent. They will check if there is anything to do, and if not they will print a message to that effect and complete successfully. E.g. create-team will check if the team already exists and if so exit as success (compared to previously where it would crash). The following commands have been updated:
3 | - migrate-repo
4 | - A generated script using `ado2gh` or `gh gei` now include the CLI version that was used to generate it.
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.15.md:
--------------------------------------------------------------------------------
1 | - `integrate-boards` no longer requires a PAT with the `All Accessible Organizations` setting
2 | - Fixed incorrect repo url in migration logs when migrating from GHES
3 | - Added `reclaim-mannequin` command to ado2gh (previously it was only available in `gh gei`)
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.16.md:
--------------------------------------------------------------------------------
1 | - Add capability to reclaim mannequins in bulk by using a CSV file
2 | - Added new command `generate-mannequin-csv`
3 | - Updated `reclaim-mannequin` command to accept `--csv` argument
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.17.md:
--------------------------------------------------------------------------------
1 | - Increased download/upload timeouts when migrating from GHES (some customers were hitting timeout errors with large repos and/or slow connections)
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.18.md:
--------------------------------------------------------------------------------
1 | - Add `download-logs` command to download migration logs
2 | - Add `--download-migration-logs` option to `generate-script` command
3 | - Log GitHub request id into the verbose log for each GitHub API call (this can be useful for GitHub support if something goes wrong)
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.19.md:
--------------------------------------------------------------------------------
1 | - Added `inventory-report` command to `ado2gh`. This command will generate a few CSV reports of all the orgs, team projects, repos and pipelines it can access. Along with some extra information that can be useful when planning a large migration (e.g. PR count per repo, most active contributer, pushes in past year, last push date, etc)
2 | - Add `--skip-releases` flag to `gh gei` for `migrate-repo` and `generate-script` commands to support skipping releases when migrating.
3 | - `wait-for-migration` progress report in both `ado2gh` and `gh gei` now logs the target repository name in addition to migration id for more readability.
4 | - Update share-service-connection command to be idempotent. If you try to share a service connection with a Team Project where it has been previously shared, the command will now do nothing and return success (previously it would error and fail).
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.2.md:
--------------------------------------------------------------------------------
1 | - Adding automated release notes to the publishing process
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.20.md:
--------------------------------------------------------------------------------
1 | - fixed a bug where `generate-script` would not properly handle Team Projects with spaces (or other invalid for github chars) in the name
2 | - fixed bug where `inventory-report` would fail if your computer's datetime format settings were dd/mm/yyyy
3 | - Support excluding releases when `--skip-releases` flag is provided in `gh gei migrate-repo` command for GHES migration path. Previously releases were excluded by default but now they are going to be included unless `--skip-releases` is provided.
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.21.md:
--------------------------------------------------------------------------------
1 | - `generate-script` command in `ado2gh` and `gh gei` will no longer generate an empty script if no migratable repos were found.
2 | - `--ado-pipeline` arg in the `ado2gh rewire-pipeline` command will now accept a pipeline name without the full pipeline path, so long as there is only one pipeline found that matches that name. If there are multiple pipelines with the same name (in different pipeline folders), you will need to provide the full pipeline path.
3 | - Resolved a bug where the `migrate-repo` command wouldn't queue a migration if the target repo had recently been renamed.
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.22.md:
--------------------------------------------------------------------------------
1 | - added `--repo-list` argument to `ado2gh generate-script`. This accepts a repos.csv file previously generated by `ado2gh inventory-report`. This can be used to split a large migration up into batches of repos.
2 | - `--github-pat` arg in the `gei reclaim-mannequin` command was renamed to `--github-target-pat` to follow the same naming convention for other commands like ` gei migrate-repo` or `gei generate-mannequin-csv`
3 | - `ado2gh inventory-report` command now also reports the compressed size of each repo in `repos.csv`.
4 | - fixed bug in `gh gei generate-script` so that it properly respects the `--no-ssl-verify` argument.
5 | - `ado2gh inventory-report` now accepts `--minimal` flag. If set, it generates the CSVs with the bare minimum info but it significantly speeds up their generation.
6 | - Added `is-pat-org-admin` field to `orgs.csv` generated by `ado2gh inventory-report` to indicate whether the PAT used to query the org data is an org admin.
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.23.md:
--------------------------------------------------------------------------------
1 | - Added `create-team` command to `gh gei`.
2 | - Reduced # of REST API calls used to avoid rate limiting challenges
3 | - Changed how we do the version check to avoid warnings about being rate limited
4 | - Checks for an environment variable GEI_DEBUG_MODE and if set to 'true' will emit additional data in the verbose log file
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.24.md:
--------------------------------------------------------------------------------
1 | - Added `--lock-source-repo` option to `gh gei migrate-repo` and `gh gei generate-script` commands. This will make the source repo read-only as part of the migration.
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.25.md:
--------------------------------------------------------------------------------
1 | - We are now shipping all functionality for Azure DevOps -> GitHub migrations as a new extension to the GitHub CLI. Run `gh extension install github/gh-ado2gh` to install. All commands are the same as the previous `ado2gh` CLI (e.g. `gh ado2gh generate-script`). If you were previously using `gh gei` to handle ADO migrations (instead of the separate `ado2gh` stand-alone CLI), that functionality has now been moved to the `gh ado2gh` extension. `gh gei` is solely used for GitHub -> GitHub migration scenarios.
2 | - We will continue shipping `ado2gh` stand-alone CLI in this release and probably the next couple, after that the only way to acquire new versions will be via the `gh` extension.
3 | - Technically in this release the ADO capabilities in `gh gei` have only been hidden from the built-in help, but will still work and give the user a warning that they should be using `gh ado2gh` instead. In a future release we will remove them entirely.
4 | - Our docs have been updated to reflect these changes and can be accessed here: https://docs.github.com/en/early-access/enterprise-importer
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.26.md:
--------------------------------------------------------------------------------
1 | - Retry all failed GET requests made to Github, Azure Devops, and Bitbucket Server.
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.27.md:
--------------------------------------------------------------------------------
1 | - 6 months ago in [v0.10](https://github.com/github/gh-gei/releases/tag/v0.10), we deprecated the --ssh flag, making it silently do nothing. Now, we’ve removed the flag entirely, so the CLI will error if you try to specify it. If you have any scripts that include the --ssh flag, you must remove that flag or the script will break.
2 | - fixed a bug where secrets were not getting scrubbed from the logs in some circumstances
3 | - If `create-team` fails when linking an IdP group with an HTTP 400, we will retry
4 | - If `create-team` fails when removing the initial team member, it will retry
5 | - In v0.25 we started publishing `ado2gh` as an extension to the `gh` CLI. However, we didn't update `ado2gh generate-script` to use the new syntax in the generated migration script. Now it will, and you will need the `gh ado2gh` extension installed in order to run the generated migration script.
6 | - Added `--ghes-api-url` as an optional arg to the `grant-migrator-role` and `revoke-migrator-role` commands for both `ado2gh` and `gei`.
7 | - Added AWS support for archive uploads to `gei` using the `--aws-bucket-name`, `--aws-access-key` and `--aws-secret-key` arguments.
8 |
--------------------------------------------------------------------------------
/releasenotes/v0.28.md:
--------------------------------------------------------------------------------
1 | - Fixed a bug where uploading archives to S3 still required `--azure-storage-connection-string` to be passed.
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.29.md:
--------------------------------------------------------------------------------
1 | - Added additional retry logic covering the case when polling for migration status fails for any reason (along with a few other situations)
2 | - Added `--aws-bucket-name` to `gh gei generate-script` and removed `--azure-storage-connection-string`.
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.3.md:
--------------------------------------------------------------------------------
1 | - 2x timestamped log files are generated on every run, timestamp.octoshift.log and timestamp.octoshift.verbose.log. A future release will include additional functionality to avoid the log file sprawl this may result in.
2 | - All commands support --verbose flag, this flag only controls what is output to the console. The verbose log file will always be created
3 | - Every API call will generate verbose output with the request and response contents
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.30.md:
--------------------------------------------------------------------------------
1 | - Added logging to `gh gei wait-for-migration` and `gh gei migrate-org --wait`, showing the number of repos that have been migrated so far
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.31.md:
--------------------------------------------------------------------------------
1 | - introduced a new command `gh gei migrate-secret-alerts` which migrates the state and resolution of secret scanning alerts. This is useful if you have existing secret scanning alerts which have been closed (e.g. revoked, false positive, etc). The state (closed) and resolution will be migrated over to the target repo with this command.
2 | - make `gh gei migrate-org` visible in the help menu as this is now a published feature of GEI [see more](https://docs.github.com/en/early-access/enterprise-importer/migrating-organizations-with-github-enterprise-importer). We are now capable of moving an org with all their repositories, teams and certain cross repository references by running one command. ( Currently does not support GitHub Enterprise Server )
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.32.md:
--------------------------------------------------------------------------------
1 | - Improve error messages when the specified target organization or enterprise cannot be found
2 | - Mask the value for `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` parameters in log output
3 | - Fix log output so we don't say we've finished upload to Azure Blob Storage when you're actually using Amazon S3
4 | - Extend the expiration of blob storage signed URLs from 24hrs to 48hrs so migration can still be successful even if there is a long queue of migrations
5 | - Skip the upload to Azure/AWS blob storage when migrating from GHES 3.8+, as GHES will now handle putting the archives into blob storage
6 | - Fixed a bug where bad credentials were incorrectly being treated as rate-limit errors
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.33.md:
--------------------------------------------------------------------------------
1 | - Added support for x86 Windows machines with new `windows-386` build
2 | - Added logic to check if a target repo exists before generating GHES archives
3 | - Fixed reclaiming a single mannequin using `reclaim-mannequin` with the `--mannequin-user` and `--target-user` parameters
4 | - Added logic to ensure target org exists before generating GHES archives
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.34.md:
--------------------------------------------------------------------------------
1 | - Create shared access signature (SAS) with read-only permissions - not read-write - when generating Azure Blob Storage URL
2 | - Fixes bug where CLI would crash if the source was GHAE (while trying to parse the version)
3 | - Add support for authenticating with AWS session tokens when using AWS S3 for archive upload in `gh gei` and `gh bbs2gh`. When specifying a session token, the AWS region must also be specified.
4 | - Make parallel migrations scripts generated by `gh gei generate-script` and `gh ado2gh generate-script` more resilient by not halting the entire script if queuing a repo migration fails.
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.35.md:
--------------------------------------------------------------------------------
1 | - Fix `gh bbs2gh grant-migrator-role` so it doesn't throw `System.InvalidOperationException`
2 | - Rename `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` environment variables to `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` respectively to align with the environment variables that the AWS CLI already uses. Old environment variables are still supported but they will be removed in future.
3 | - Send a `User-Agent` header with the current CLI version when downloading migration archives from GitHub Enterprise Server
4 | - Add support for migration archives larger than 2GB when using the blob storage flow
5 | - Add `--no-ssh-verify` option to `gh bbs2gh generate-script` and `gh bbs2gh migrate-repo` commands to support migrating from a Bitbucket Server or Bitbucket Data Center instance that uses a self-signed SSL certificate
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.36.md:
--------------------------------------------------------------------------------
1 | - Adds retry logic during GHES archive generation in cases of transient failure
2 | - Added log output linking to migration log URL after migration completes
3 | - Add support for specifying `--archive-download-host` with `gh bbs2gh migrate-repo` and `gh bbs2gh generate-script`, rather than taking the host from the `--bbs-server-url`
4 | - Improve handling of GraphQL errors, throwing an exception with the specific error message returned by the API
5 | - Validate AWS region when using Amazon S3 to upload the migration archive in `gh gei` and `gh bbs2gh`
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.37.md:
--------------------------------------------------------------------------------
1 | - Improve error message when `migrate-repo` is used with a target personal access token (PAT) with insufficient permissions
2 | - Ensure `--no-ssl-verify` flag is honored when downloading archives from GHES
3 | - `--bbs-project` and `--bbs-repo` are now both required in `gh bbs2gh migrate-repo` command when `--bbs-server-url` is set
4 | - Added `--keep-archive` flag to `gh gei migrate-repo` and `gh gei generate-script`. When migrating from GHES this will skip the step where we delete the archive from your machine, leaving it around as a local file.
5 | - Continue to next mannequin mapping in `gh gei reclaim-mannequin --csv` if a username doesn't exist
6 | - Display more helpful message when the Bitbucket export archive is not found when using `gh bbs2gh migrate-repo`
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.38.md:
--------------------------------------------------------------------------------
1 | - Introduce a new command `gh gei migrate-code-scanning-alerts` which migrates all code-scanning analysis and alert states for the default branch. This is useful if you want to migrate the history of code-scanning alerts together with their current state (open, reopened, fixed). For dismissed alerts, the dismissed-reason (e.g. won't fix, false positive etc) and dismissed-comment will also be migrated to the target repo.
2 | - Update `gh bbs2gh generate-script` so it supports more than 25 projects/repos
3 | - Rename `--bbs-project-key` to `--bbs-project` in `gh bbs2gh generate-script` for consistency
4 | - Fix a bug where `create-team` might not work due to a race condition
5 | - When using `gh ado2gh generate-script` or `gh bbs2gh generate-script` the script will now validate that the necessary environment variables are set
6 | - Make API calls to GitHub.com that require pagination more resilient by retrying on HTTP failures
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.39.md:
--------------------------------------------------------------------------------
1 | - Fixed a bug where ADO Team Projects or Organizations with special characters would fail to migrate
2 | - When using `gh gei generate-script` the script will now validate that the necessary environment variables are set
3 | - More robust retry logic, especially on http request timeouts
4 | - Retry GHES archive generation process in `gh gei migrate-repo` in case of any failure
5 | - Changed the default behavior of `migrate-repo` and `migrate-org` to wait for the migration to finish (previously the default was to only queue it unless you passed `--wait`). If you want the previous default behavior of queuing it only (e.g. for parallel scripts that queue many migrations at once) there is a new `--queue-only` option. The `--wait` option still works but is now obsolete and will print a warning if used, and will be removed in a future version. `generate-script` commands have all been updated to generate scripts with the new options/defaults. Any already existing migration scripts that relied on the default (i.e. parallel) behavior, will continue to work but will now run sequentially instead of in parallel. They should be updated to pass in `--queue-only` to retain the previous parallel behavior (or re-generated with the updated `generate-script` command).
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.4.md:
--------------------------------------------------------------------------------
1 | - Added descriptions to all commands in the built-in CLI help
2 | - add-team-to-repo now includes built-in help listing the valid values for the --role argument (octoshift add-team-to-repo --help), the CLI also validates the value of --role before attempting to run.
3 | - integrate-boards command now expects a single call per repo (instead of single call per team project with a list of repos). This will enable splitting a single team project into multiple batches for migration purposes. If no Boards-GitHub connection exists it will create it, otherwise it will add the repo to the existing connection. It assumes either 0 or 1 boards connection exists per team project (multiple connections in a single team project has not been tested).
4 | - migrate-repo now uses the GH_PAT for git push. The GH_PAT needs to have the repo scope in order for the git push to work.
--------------------------------------------------------------------------------
/releasenotes/v0.40.md:
--------------------------------------------------------------------------------
1 | - Fixed a bug when migrating from GHES where we would do a bunch of unnecessary retries at the start making things slower than they needed to be
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.41.md:
--------------------------------------------------------------------------------
1 | - `migrate-repo` commands now take a `--target-repo-visibility` flag (`"public"`, `"private"`, or `"internal"`; defaults to `"private"`) to set the visibility of the imported repo
2 | - `gh gei generate-script` will now inspect the source repo visibility and add the appropriate `--target-repo-visibility` flag to the generated script. `ado2gh generate-script` and `bbs2gh generate-script` will include the `--target-repo-visibility` flag in the generated script but it will always be set to private.
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.42.md:
--------------------------------------------------------------------------------
1 | - Include the migration ID in the default output filename when running `download-logs`
2 | - Include the date with the timestamp when writing to the log
3 | - When blob storage credentials are provided to the CLI but will not be used for a GHES migration, log a clear warning, not an info message
4 | - Fix support for Azure Blob Storage in `bbs2gh` migrations when the archive is larger than 2GB
5 | - Unhide the `--archive-download-host` argument in the documentation for `gh bbs2gh migrate-repo` and `gh bbs2gh generate-script`
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.43.md:
--------------------------------------------------------------------------------
1 | - Add new '--skip-invitation' flag for `reclaim-mannequin` to allow EMU organizations to reclaim mannequins without an email invitation
2 | - Write warnings to log and console if GitHub is experiencing an availability incident.
3 | - Improve the error thrown when you have insufficient permissions for the target GitHub organization to explicitly mention the relevant organization
4 | - Write log output prior to making API calls in wait-for-migration commands
5 | - Support forked repositories in Bitbucket Server migrations
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.44.md:
--------------------------------------------------------------------------------
1 |
2 | - Hide the `reclaim-mannequin --skip-invitation` option from documentation, since it's still under development and not yet available
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.45.md:
--------------------------------------------------------------------------------
1 | - Improve log sanitization to also remove secret values that have been URL encoded
2 | - Warn when the `--ssh-port` argument for `bbs2gh migrate-repo` and `bbs2gh generate-script` is set to the default port for Git operations
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.46.md:
--------------------------------------------------------------------------------
1 | - Add additional error handling to `reclaim-mannequin` process
2 | - Removed ability to use `gh gei` to migrate from ADO -> GH. You must use `gh ado2gh` to do this now. This was long since obsolete, but was still available via hidden args - which have now been removed.
3 | - Add `bbs2gh inventory-report` command to write data available for migrations in CSV form
4 |
--------------------------------------------------------------------------------
/releasenotes/v0.47.md:
--------------------------------------------------------------------------------
1 | - Include the `databaseId` (GUID) in the verbose logs when starting a migration
2 | - Fix recommended `gh gei`, `gh bbs2gh` and `gh ado2gh` commands in log output
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.48.md:
--------------------------------------------------------------------------------
1 | - __BREAKING CHANGE:__ Require the AWS region to always be specified with the `--aws-region` argument or `AWS_REGION` environment variable if using AWS S3 for blob storage. Previously, this was optional (with a warning) if you weren't specifying an AWS session token.
2 | - __BREAKING CHANGE:__ Drop support for deprecated `AWS_ACCESS_KEY` and `AWS_SECRET_KEY` environment variables in `gh gei` and `gh bbs2gh`. The AWS S3 credentials can now only be configured using the industry-standard `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` variables or command line arguments.
3 | - __BREAKING CHANGE__: Require the Bitbucket Server URL, project key and repo to always be provided for `bbs2gh migrate-repo`, even if using the upload-and-migrate (`--archive-path`) or migrate-only (`--archive-url`) flows
4 | - Increase timeouts in archive uploads to AWS to prevent timeouts during large uploads
5 |
--------------------------------------------------------------------------------
/releasenotes/v0.49.md:
--------------------------------------------------------------------------------
1 | - Validate that the `--ghes-api-url` is a valid URL in `gh gei generate-script`
2 | - Allow CLI to fail fast when an unauthorized token is provided by preventing retry logic on 401 errors
3 | - Improve performance when reclaiming a single mannequin with `reclaim-mannequin`
4 | - Improve logging for `reclaim-mannequin` command
5 | - Ensure that CLI runs have separate log files, even if they start at the same time, by adding the process ID to the filenames
6 |
--------------------------------------------------------------------------------
/releasenotes/v0.5.md:
--------------------------------------------------------------------------------
1 | - generate-script with the --repos-only option no longer requires the Build scope on the ADO PAT
2 |
--------------------------------------------------------------------------------
/releasenotes/v0.6.1.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 | - fixed bug where using --ssh flag would cause the migration to crash
3 |
--------------------------------------------------------------------------------
/releasenotes/v0.6.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | - Renamed the CLI from octoshift to ado2gh to indicate that this one is specifically for Azure DevOps -> GitHub migrations (in the future there will be additional CLI's for other migration scenarios)
4 | - Released an extension for the official GitHub CLI that adds support for GitHub -> GitHub migrations (GHEC only for now). To install run: `gh extension install github/gh-gei`. To use run: `gh gei --help`
5 | - Automatically remove secrets from log files and console output (previously the verbose logs would contain your PAT's)
6 | - Added --ssh option to generate-script and migrate-repo commands (in both ado2gh and gh). This forces the migration to use an older version of the API's that uses SSH to push the repos into GitHub. The newer API's use HTTPS instead. However some customers have been running into problems with some repos that work fine using the older SSH API's. In the future this option will be deprecated once the issues with the HTTPS-based API's are resolved.
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.7.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | - Do not log the error's stack trace to console in non-verberse mode.
4 | - Show a generic error message instead of the actual one for unhandled exceptions in non-verbose mode.
5 | - Exit code is now 1 instead of 0 in case of an error.
6 | - Errors are written to std error instead of std out.
7 | - Adding Support to get multi page results from Github API.
8 | - The Github to Github migrations are no longer limited to 30 repos.
9 |
--------------------------------------------------------------------------------
/releasenotes/v0.8.md:
--------------------------------------------------------------------------------
1 | - Adds the ability to migrate ADO repos using the `gh gei` CLI. This overlaps with some of the capabilities of ado2gh, but the `gh gei` will not include all the extra ADO migration capabilities like re-wiring pipelines and boards integration.
2 | - `gh gei generate-script` now has an `--ado-source-org` option
3 | - `gh gei migrate-repo` now has `--ado-source-org` and `--ado-team-project` options
4 | - Added `grant-migrator-role` and `revoke-migrator-role` commands to `gh gei`
5 | - Add gei command path for generating a migration archive `gh gei generate-archive` which uses the migration api on that instance to generate two archives of data, the metadata for a repository and the git data for that repository (this will primarily be useful for migrations from GHES)
6 | - The script generated from generate-script will now stop on the first command that fails. Previously this script could be renamed between .ps1 and .sh, but with this change it will now only work as a .ps1 script.
7 |
--------------------------------------------------------------------------------
/releasenotes/v0.9.md:
--------------------------------------------------------------------------------
1 | - Remove `gh gei generate-archive`
2 | - Update `gh gei migrate-repo` to allow for migrations from GHES instances. When `--ghes-api-url` is passed in, it requires an Azure Blob Storage connection string `--azure-storage-connection-string` and an optional flag to disable SSL verification `--no-ssl-verify`. This migration path generates migration archives on the source, uploads them to Azure Blob Storage using the connection string, then kicks off a GitHub Enterprise Importer migration using the uploaded migration archives.
3 | - Modify `gh gei migrate-repo` to optionally accept two pre-generated archive urls to start a migration (not commonly used) and a target api url parameter
4 | - Fixed a bug where `configure-autolink` command would fail if your ADO team project had a space in it
5 | - Update `gh gei generate-script` to allow for migrations from GHES by passing the options `--ghes-api-url`, `--azure-storage-connection-string`, `--no-ssl-verify`.
6 |
--------------------------------------------------------------------------------
/releasenotes/v1.0.0.md:
--------------------------------------------------------------------------------
1 | - __BREAKING CHANGE__: Drop deprecated `--wait` option for `migrate-repo` and `migrate-org` commands
2 | - Allow Enterprise Managed Users (EMU) organizations to skip the invitation process in the `reclaim-mannequin` command with the `--skip-invitation` option
3 | - Fix target repo existence check in GitHub Enterprise Server migrations so it doesn't error if the target repo used to exist, but has been renamed
4 | - Fail fast if the target GitHub repo already exists in `bbs2gh migrate-repo`
5 |
--------------------------------------------------------------------------------
/releasenotes/v1.1.0.md:
--------------------------------------------------------------------------------
1 | - Log migration warnings count after a migration completes
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.10.0.md:
--------------------------------------------------------------------------------
1 | - Adds `--git-archive-path` and `--metadata-archive-path` options to `gh gei migrate-repo` for uploading (to selected storage) and migrating
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.10.1.md:
--------------------------------------------------------------------------------
1 | - catches exception when Target is not a member of the organization and the `--skip-invitation` flag is enabled
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.11.0.md:
--------------------------------------------------------------------------------
1 | - Add progress report to `gh [gei|bbs2gh] migrate-repo` command when uploading migration archives to Azure Blob or AWS S3
2 | - `gh gei migrate-repo` now allows using either `--ghes-api-url` or archive paths (`--git-archive-path` and `--metadata-archive-path`).
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.12.0.md:
--------------------------------------------------------------------------------
1 | - Update validation error messages for `gh bbs2gh migrate-repo` command when generating an archive is not required.
2 | - `gh gei migrate-code-scanning-alerts` now skips a not found code scanning analysis and continues with the rest.
3 | - Updated Secret Scanning Alerts migration (`gh gei migrate-secret-alerts`) command to match on all location types. Now includes: issues, pull requests.
4 |
--------------------------------------------------------------------------------
/releasenotes/v1.13.0.md:
--------------------------------------------------------------------------------
1 | - `gh gei migrate-repo` logs the git and metadata archive download paths when `--keep-archive` is used.
2 | - Redact sensitive query arguments in SAS URLs
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.14.0.md:
--------------------------------------------------------------------------------
1 | - Improved location matching logic for Secret Scanning alert locations in - `gh gei migrate-secret-alerts`.
2 | - Fixed a bug in `gh gei migrate-secret-alerts` where alerts with locations of non `commit` and `wiki_commit` type were not matched correctly.
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.15.0.md:
--------------------------------------------------------------------------------
1 | - Improve resilience of `gh gei migrate-repo` and `gh bb2gh migrate-repo` commands when uploading migration archives to GitHub owned storage.
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.15.1.md:
--------------------------------------------------------------------------------
1 | Adjusting the release version so we can go to 1.15.1
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.2.0.md:
--------------------------------------------------------------------------------
1 | - When running migrations with `gh bbs2gh` and migrations from GitHub Enterprise Server with `gh gei`, create the migration source before starting the export
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.2.1.md:
--------------------------------------------------------------------------------
1 | - Extend timeout from 10 hours to 20 hours when generating GitHub Enterprise Server migration archives in `gh gei migrate-repo`
2 |
--------------------------------------------------------------------------------
/releasenotes/v1.3.0.md:
--------------------------------------------------------------------------------
1 | - Add a warning to help users who accidentally end up in the "running on the Bitbucket instance" flow in `bbs2gh migrate-repo`
2 | - Fix version check so that a version later than the latest version published on GitHub isn't considered old
3 | - Fix `download-logs` commands suggested by `bbs2gh migrate-repo` and `ado2gh migrate-repo` to use correct arguments
4 | - Fix version number exposed in headers and `generate-script` scripts, removing the [revision component](https://learn.microsoft.com/en-us/dotnet/api/system.version?view=net-7.0#remarks) so it matches the published version
5 |
--------------------------------------------------------------------------------
/releasenotes/v1.4.0.md:
--------------------------------------------------------------------------------
1 |
2 | - Skip GitHub status checking on startup if environment variable `GEI_SKIP_STATUS_CHECK` is set to `true`
3 | - Redact `?token=` querystring parameter from GHES <3.8 archive URLs in logs
4 |
--------------------------------------------------------------------------------
/releasenotes/v1.5.0.md:
--------------------------------------------------------------------------------
1 | - Redact `X-Amz-Credential` querystring parameters in AWS S3 URLs included in logs
2 | - When redacting sensitive patterns in log output, use a non-case sensitive search
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.6.0.md:
--------------------------------------------------------------------------------
1 |
2 | - Skip latest version check on startup if environment variable `GEI_SKIP_VERSION_CHECK` is set to `true`
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.7.0.md:
--------------------------------------------------------------------------------
1 | - Add new `gh [gei|bbs2gh|ado2gh] abort-migration` command to cancel a migration that is in the queue or in progress
2 | - Fix suggested command for downloading logs in `gh gei migrate-repo` log output
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.7.1.md:
--------------------------------------------------------------------------------
1 | - Update `bbs2gh inventory-report` help text to document that personal repositories owned by users are not supported
2 | - Update `ado2gh lock-repo` help text to document the scopes required for your Azure DevOps personal access token
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.8.0.md:
--------------------------------------------------------------------------------
1 | - Add `--target-api-url` to commonly used commands to support newer GitHub migration paths.
2 | - Fixed `gh ado2gh rewire-pipeline` command for ADO Team Projects with more than 10,000 Build Definitions.
3 |
--------------------------------------------------------------------------------
/releasenotes/v1.9.0.md:
--------------------------------------------------------------------------------
1 |
2 | - Add `--use-github-storage` to `gh [gei|bbs2gh] migrate-repo` command to support uploading to a GitHub owned storage
3 | - Add `--use-github-storage` to `gh [gei|bbs2gh] generate-script` command to support uploading to a GitHub owned storage
4 |
--------------------------------------------------------------------------------
/releasenotes/v1.9.1.md:
--------------------------------------------------------------------------------
1 | - `gh gei migrate-secret-alerts` copies the resolver name from the original alert to the resolution comment of the matching target alert. The comment follows this format: `[@actorname] original comment`.
2 |
--------------------------------------------------------------------------------
/src/.idea/.idea.OctoshiftCLI/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Rider ignored files
5 | /contentModel.xml
6 | /projectSettingsUpdater.xml
7 | /modules.xml
8 | /.idea.OctoshiftCLI.iml
9 | # Editor-based HTTP Client requests
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/src/.idea/.idea.OctoshiftCLI/.idea/.name:
--------------------------------------------------------------------------------
1 | OctoshiftCLI
--------------------------------------------------------------------------------
/src/.idea/.idea.OctoshiftCLI/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/.idea/.idea.OctoshiftCLI/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ../../OctoshiftCLI
6 | ../../gh-gei
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/.idea/.idea.OctoshiftCLI/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/Octoshift/ArchiveMigrationStatus.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI
2 | {
3 | public static class ArchiveMigrationStatus
4 | {
5 | public const string Pending = "pending";
6 | public const string Exporting = "exporting";
7 | public const string Exported = "exported";
8 | public const string Failed = "failed";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Octoshift/CliContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace OctoshiftCLI
4 | {
5 | public static class CliContext
6 | {
7 | private static string _rootCommand;
8 | private static string _executingCommand;
9 |
10 | public static string RootCommand
11 | {
12 | set => _rootCommand = _rootCommand is null
13 | ? value
14 | : throw new InvalidOperationException("Value can only be set once.");
15 | get => _rootCommand;
16 | }
17 |
18 | public static string ExecutingCommand
19 | {
20 | set => _executingCommand = _executingCommand is null
21 | ? value
22 | : throw new InvalidOperationException("Value can only be set once.");
23 | get => _executingCommand;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Octoshift/CodeScanningAlertState.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI
2 | {
3 | public static class CodeScanningAlertState
4 | {
5 | public const string Open = "open";
6 | public const string Dismissed = "dismissed";
7 |
8 | public const string FalsePositive = "false positive";
9 | public const string WontFix = "won't fix";
10 | public const string UsedInTests = "used in tests";
11 |
12 | public static bool IsOpenOrDismissed(string state) => state?.Trim().ToLower() is Open or Dismissed;
13 |
14 | public static bool IsDismissed(string state) => state?.Trim().ToLower() is Dismissed;
15 |
16 | public static bool IsValidDismissedReason(string reason) =>
17 | reason?.Trim().ToLower() is
18 | WontFix or UsedInTests or FalsePositive;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/AbortMigration/AbortMigrationCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Extensions;
2 | using OctoshiftCLI.Services;
3 |
4 | namespace OctoshiftCLI.Commands.AbortMigration;
5 |
6 | public class AbortMigrationCommandArgs : CommandArgs
7 | {
8 | public string MigrationId { get; set; }
9 | [Secret]
10 | public string GithubPat { get; set; }
11 | public string TargetApiUrl { get; set; }
12 |
13 | public override void Validate(OctoLogger log)
14 | {
15 | const string repoMigrationIdPrefix = "RM_";
16 |
17 | if (MigrationId.IsNullOrWhiteSpace())
18 | {
19 | throw new OctoshiftCliException("--migration-id must be provided");
20 | }
21 |
22 | if (!MigrationId.StartsWith(repoMigrationIdPrefix))
23 | {
24 | throw new OctoshiftCliException($"Invalid migration ID: {MigrationId}. Only repository migration IDs starting with RM_ are supported.");
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/AbortMigration/AbortMigrationCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using OctoshiftCLI.Services;
4 |
5 |
6 | namespace OctoshiftCLI.Commands.AbortMigration;
7 |
8 | public class AbortMigrationCommandHandler : ICommandHandler
9 | {
10 |
11 | private readonly OctoLogger _log;
12 | private readonly GithubApi _githubApi;
13 |
14 | public AbortMigrationCommandHandler(OctoLogger log, GithubApi githubApi)
15 | {
16 | _log = log;
17 | _githubApi = githubApi;
18 | }
19 |
20 | public async Task Handle(AbortMigrationCommandArgs args)
21 | {
22 | if (args is null)
23 | {
24 | throw new ArgumentNullException(nameof(args));
25 | }
26 |
27 | await AbortRepositoryMigration(args.MigrationId);
28 | }
29 |
30 | private async Task AbortRepositoryMigration(string migrationId)
31 | {
32 | var success = await _githubApi.AbortMigration(migrationId);
33 |
34 | if (!success)
35 | {
36 | _log.LogError($"Failed to abort migration {migrationId}");
37 | return;
38 | }
39 |
40 | _log.LogInformation($"Migration {migrationId} was cancelled");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/CommandBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.CommandLine;
3 |
4 | namespace OctoshiftCLI.Commands;
5 |
6 | public abstract class CommandBase : Command where TArgs : CommandArgs where THandler : ICommandHandler
7 | {
8 | protected CommandBase(string name, string description = null) : base(name, description) { }
9 |
10 | public abstract THandler BuildHandler(TArgs args, IServiceProvider sp);
11 | }
12 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/CreateTeam/CreateTeamCommandArgs.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI.Commands.CreateTeam;
2 |
3 | public class CreateTeamCommandArgs : CommandArgs
4 | {
5 | public string GithubOrg { get; set; }
6 | public string TeamName { get; set; }
7 | public string IdpGroup { get; set; }
8 | [Secret]
9 | public string GithubPat { get; set; }
10 | public string TargetApiUrl { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/DownloadLogs/DownloadLogsCommandArgs.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace OctoshiftCLI.Commands.DownloadLogs;
3 |
4 | public class DownloadLogsCommandArgs : CommandArgs
5 | {
6 | public string GithubOrg { get; set; }
7 | public string GithubRepo { get; set; }
8 | public string GithubApiUrl { get; set; }
9 | [Secret]
10 | public string GithubPat { get; set; }
11 | public string MigrationLogFile { get; set; }
12 | public bool Overwrite { get; set; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace OctoshiftCLI.Commands.GenerateMannequinCsv;
4 |
5 | public class GenerateMannequinCsvCommandArgs : CommandArgs
6 | {
7 | public string GithubOrg { get; set; }
8 | public FileInfo Output { get; set; }
9 | public bool IncludeReclaimed { get; set; }
10 | [Secret]
11 | public string GithubPat { get; set; }
12 | public string TargetApiUrl { get; set; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Extensions;
2 | using OctoshiftCLI.Services;
3 |
4 | namespace OctoshiftCLI.Commands.GrantMigratorRole;
5 |
6 | public class GrantMigratorRoleCommandArgs : CommandArgs
7 | {
8 | public string GithubOrg { get; set; }
9 | public string Actor { get; set; }
10 | public string ActorType { get; set; }
11 | [Secret]
12 | public string GithubPat { get; set; }
13 | public string GhesApiUrl { get; set; }
14 | public string TargetApiUrl { get; set; }
15 |
16 | public override void Validate(OctoLogger log)
17 | {
18 | ActorType = ActorType?.ToUpper();
19 |
20 | if (ActorType is "TEAM" or "USER")
21 | {
22 | log?.LogInformation("Actor type is valid...");
23 | }
24 | else
25 | {
26 | throw new OctoshiftCliException("Actor type must be either TEAM or USER.");
27 | }
28 |
29 | if (GhesApiUrl.HasValue() && TargetApiUrl.HasValue())
30 | {
31 | throw new OctoshiftCliException("Only one of --ghes-api-url or --target-api-url can be set at a time.");
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using OctoshiftCLI.Services;
4 |
5 | namespace OctoshiftCLI.Commands.GrantMigratorRole;
6 |
7 | public class GrantMigratorRoleCommandHandler : ICommandHandler
8 | {
9 | private readonly OctoLogger _log;
10 | private readonly GithubApi _githubApi;
11 |
12 | public GrantMigratorRoleCommandHandler(OctoLogger log, GithubApi githubApi)
13 | {
14 | _log = log;
15 | _githubApi = githubApi;
16 | }
17 |
18 | public async Task Handle(GrantMigratorRoleCommandArgs args)
19 | {
20 | if (args is null)
21 | {
22 | throw new ArgumentNullException(nameof(args));
23 | }
24 |
25 | _log.LogInformation("Granting migrator role ...");
26 |
27 | var githubOrgId = await _githubApi.GetOrganizationId(args.GithubOrg);
28 | var success = await _githubApi.GrantMigratorRole(githubOrgId, args.Actor, args.ActorType);
29 |
30 | if (success)
31 | {
32 | _log.LogSuccess($"Migrator role successfully set for the {args.ActorType} \"{args.Actor}\"");
33 | }
34 | else
35 | {
36 | _log.LogError($"Migrator role couldn't be set for the {args.ActorType} \"{args.Actor}\"");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/ICommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace OctoshiftCLI.Commands;
4 |
5 | public interface ICommandHandler where TArgs : CommandArgs
6 | {
7 | Task Handle(TArgs args);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Services;
2 |
3 | namespace OctoshiftCLI.Commands.ReclaimMannequin;
4 |
5 | public class ReclaimMannequinCommandArgs : CommandArgs
6 | {
7 | public string GithubOrg { get; set; }
8 | public string Csv { get; set; }
9 | public string MannequinUser { get; set; }
10 | public string MannequinId { get; set; }
11 | public string TargetUser { get; set; }
12 | public bool Force { get; set; }
13 | public bool NoPrompt { get; set; }
14 | [Secret]
15 | public string GithubPat { get; set; }
16 | public bool SkipInvitation { get; set; }
17 | public string TargetApiUrl { get; set; }
18 | public override void Validate(OctoLogger log)
19 | {
20 | if (string.IsNullOrEmpty(Csv) && (string.IsNullOrEmpty(MannequinUser) || string.IsNullOrEmpty(TargetUser)))
21 | {
22 | throw new OctoshiftCliException($"Either --csv or --mannequin-user and --target-user must be specified");
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/RevokeMigratorRole/RevokeMigratorRoleCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Extensions;
2 | using OctoshiftCLI.Services;
3 |
4 | namespace OctoshiftCLI.Commands.RevokeMigratorRole;
5 |
6 | public class RevokeMigratorRoleCommandArgs : CommandArgs
7 | {
8 | public string GithubOrg { get; set; }
9 | public string Actor { get; set; }
10 | public string ActorType { get; set; }
11 | [Secret]
12 | public string GithubPat { get; set; }
13 | public string GhesApiUrl { get; set; }
14 | public string TargetApiUrl { get; set; }
15 |
16 | public override void Validate(OctoLogger log)
17 | {
18 | ActorType = ActorType?.ToUpper();
19 |
20 | if (ActorType is "TEAM" or "USER")
21 | {
22 | log?.LogInformation("Actor type is valid...");
23 | }
24 | else
25 | {
26 | throw new OctoshiftCliException("Actor type must be either TEAM or USER.");
27 | }
28 |
29 | if (GhesApiUrl.HasValue() && TargetApiUrl.HasValue())
30 | {
31 | throw new OctoshiftCliException("Only one of --ghes-api-url or --target-api-url can be set at a time.");
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/RevokeMigratorRole/RevokeMigratorRoleCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using OctoshiftCLI.Services;
4 |
5 | namespace OctoshiftCLI.Commands.RevokeMigratorRole;
6 |
7 | public class RevokeMigratorRoleCommandHandler : ICommandHandler
8 | {
9 | private readonly OctoLogger _log;
10 | private readonly GithubApi _githubApi;
11 |
12 | public RevokeMigratorRoleCommandHandler(OctoLogger log, GithubApi githubApi)
13 | {
14 | _log = log;
15 | _githubApi = githubApi;
16 | }
17 |
18 | public async Task Handle(RevokeMigratorRoleCommandArgs args)
19 | {
20 | if (args is null)
21 | {
22 | throw new ArgumentNullException(nameof(args));
23 | }
24 |
25 | _log.LogInformation("Granting migrator role ...");
26 |
27 | var githubOrgId = await _githubApi.GetOrganizationId(args.GithubOrg);
28 | var success = await _githubApi.RevokeMigratorRole(githubOrgId, args.Actor, args.ActorType);
29 |
30 | if (success)
31 | {
32 | _log.LogSuccess($"Migrator role successfully revoked for the {args.ActorType} \"{args.Actor}\"");
33 | }
34 | else
35 | {
36 | _log.LogError($"Migrator role couldn't be revoked for the {args.ActorType} \"{args.Actor}\"");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/SecretAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace OctoshiftCLI.Commands
4 | {
5 | [AttributeUsage(AttributeTargets.Property)]
6 | public sealed class SecretAttribute : Attribute
7 | {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Octoshift/Commands/WaitForMigration/WaitForMigrationCommandArgs.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Extensions;
2 | using OctoshiftCLI.Services;
3 |
4 | namespace OctoshiftCLI.Commands.WaitForMigration;
5 |
6 | public class WaitForMigrationCommandArgs : CommandArgs
7 | {
8 | public string MigrationId { get; set; }
9 | [Secret]
10 | public string GithubPat { get; set; }
11 | public string TargetApiUrl { get; set; }
12 |
13 | public const string REPO_MIGRATION_ID_PREFIX = "RM_";
14 | public const string ORG_MIGRATION_ID_PREFIX = "OM_";
15 |
16 | public override void Validate(OctoLogger log)
17 | {
18 | if (MigrationId.IsNullOrWhiteSpace())
19 | {
20 | throw new OctoshiftCliException("MigrationId must be provided");
21 | }
22 |
23 | if (!MigrationId.StartsWith(REPO_MIGRATION_ID_PREFIX) && !MigrationId.StartsWith(ORG_MIGRATION_ID_PREFIX))
24 | {
25 | throw new OctoshiftCliException($"Invalid migration id: {MigrationId}");
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Octoshift/Contracts/ISourceGithubApiFactory.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Services;
2 |
3 | namespace OctoshiftCLI.Contracts;
4 |
5 | public interface ISourceGithubApiFactory
6 | {
7 | GithubApi Create(string apiUrl = null, string sourcePersonalAccessToken = null);
8 | GithubApi CreateClientNoSsl(string apiUrl = null, string sourcePersonalAccessToken = null);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Octoshift/Contracts/ITargetGithubApiFactory.cs:
--------------------------------------------------------------------------------
1 | using OctoshiftCLI.Services;
2 |
3 | namespace OctoshiftCLI.Contracts;
4 |
5 | public interface ITargetGithubApiFactory
6 | {
7 | GithubApi Create(string apiUrl = null, string targetPersonalAccessToken = null);
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Contracts/IVersionProvider.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI.Contracts;
2 |
3 | public interface IVersionProvider
4 | {
5 | string GetCurrentVersion();
6 | string GetVersionComments();
7 | }
8 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/AssemblyExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.CommandLine;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace OctoshiftCLI.Extensions;
8 |
9 | public static class AssemblyExtensions
10 | {
11 | internal static IEnumerable GetAllDescendantsOfCommandBase(this Assembly assembly) =>
12 | assembly?
13 | .GetTypes()
14 | .Where(t =>
15 | t.IsClass &&
16 | t.IsAssignableTo(typeof(Command)))
17 | ?? throw new ArgumentNullException(nameof(assembly));
18 | }
19 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/CommandLineOptionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.CommandLine;
2 |
3 | namespace OctoshiftCLI.Extensions;
4 |
5 | public static class CommandLineOptionExtensions
6 | {
7 | public static string GetLogFriendlyName(this Option option) => option?.Name.ToUpper().Replace("-", " ");
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace OctoshiftCLI.Extensions
8 | {
9 | public static class EnumerableExtensions
10 | {
11 | public static async Task Sum(this IEnumerable list, Func> selector)
12 | {
13 | var result = 0;
14 |
15 | if (list is not null && selector is not null)
16 | {
17 | foreach (var item in list)
18 | {
19 | result += await selector(item);
20 | }
21 | }
22 |
23 | return result;
24 | }
25 |
26 | public static IEnumerable ToEmptyEnumerableIfNull(this IEnumerable enumerable) => enumerable ?? Enumerable.Empty();
27 |
28 | public static string GetString(this byte[] bytes) => Encoding.UTF8.GetString(bytes.ToArray());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/HttpRequestMessageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Net.Http;
4 |
5 | namespace OctoshiftCLI.Extensions
6 | {
7 | public static class HttpRequestMessageExtensions
8 | {
9 | public static HttpRequestMessage AddHeaders(this HttpRequestMessage request, Dictionary headers)
10 | {
11 | headers?.ToList().ForEach(kv => request.Headers.Add(kv.Key, kv.Value));
12 | return request;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/NumericExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI.Extensions;
2 |
3 | public static class NumericExtensions
4 | {
5 | public static string ToLogFriendlySize(this long size)
6 | {
7 | const int kilobyte = 1024;
8 | const int megabyte = 1024 * kilobyte;
9 | const int gigabyte = 1024 * megabyte;
10 |
11 | return size switch
12 | {
13 | < kilobyte => $"{size:n0} bytes",
14 | < megabyte => $"{size / (double)kilobyte:n0} KB",
15 | < gigabyte => $"{size / (double)megabyte:n0} MB",
16 | _ => $"{size / (double)gigabyte:n2} GB"
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/ObjectExtensions.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace OctoshiftCLI.Extensions
4 | {
5 | public static class ObjectExtensions
6 | {
7 | public static string ToJson(this object obj) =>
8 | obj is null ? null : JsonConvert.SerializeObject(obj);
9 |
10 | public static bool HasValue(this object obj) => obj is not null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/PropertyInfoExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 |
4 | namespace OctoshiftCLI.Extensions;
5 |
6 | public static class PropertyInfoExtensions
7 | {
8 | internal static bool HasCustomAttribute(this PropertyInfo propertyInfo) =>
9 | propertyInfo.GetCustomAttributes(typeof(T), true).Any();
10 | }
11 |
--------------------------------------------------------------------------------
/src/Octoshift/Extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace OctoshiftCLI.Extensions
7 | {
8 | public static class StringExtensions
9 | {
10 | public static StringContent ToStringContent(this string s) => new(s, Encoding.UTF8, "application/json");
11 |
12 | public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s);
13 |
14 | public static bool HasValue(this string s) => !s.IsNullOrWhiteSpace();
15 |
16 | public static bool ToBool(this string s) => bool.TryParse(s, out var result) && result;
17 |
18 | public static ulong? ToULongOrNull(this string s) => ulong.TryParse(s, out var result) ? result : null;
19 |
20 | public static string ReplaceInvalidCharactersWithDash(this string s) => s.HasValue() ? Regex.Replace(s, @"[^\w.-]+", "-", RegexOptions.Compiled | RegexOptions.CultureInvariant) : string.Empty;
21 |
22 | public static string ToWindowsPath(this string path) => path?.Replace("/", "\\");
23 |
24 | public static string ToUnixPath(this string path) => path?.Replace("\\", "/");
25 |
26 | public static string EscapeDataString(this string value) => Uri.EscapeDataString(value);
27 |
28 | public static byte[] ToBytes(this string s) => Encoding.UTF8.GetBytes(s);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Octoshift/Factories/HttpDownloadServiceFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using OctoshiftCLI.Contracts;
3 | using OctoshiftCLI.Services;
4 |
5 | namespace OctoshiftCLI.Factories;
6 |
7 | public sealed class HttpDownloadServiceFactory
8 | {
9 | private readonly OctoLogger _log;
10 | private readonly IHttpClientFactory _clientFactory;
11 | private readonly FileSystemProvider _fileSystemProvider;
12 | private readonly IVersionProvider _versionProvider;
13 |
14 | public HttpDownloadServiceFactory(OctoLogger log, IHttpClientFactory clientFactory, FileSystemProvider fileSystemProvider, IVersionProvider versionProvider)
15 | {
16 | _log = log;
17 | _clientFactory = clientFactory;
18 | _fileSystemProvider = fileSystemProvider;
19 | _versionProvider = versionProvider;
20 | }
21 |
22 | public HttpDownloadService CreateDefaultWithRedirects()
23 | {
24 | var httpClient = _clientFactory.CreateClient();
25 |
26 | return new HttpDownloadService(_log, httpClient, _fileSystemProvider, _versionProvider);
27 | }
28 |
29 | public HttpDownloadService CreateDefault()
30 | {
31 | var httpClient = _clientFactory.CreateClient("Default");
32 |
33 | return new HttpDownloadService(_log, httpClient, _fileSystemProvider, _versionProvider);
34 | }
35 |
36 | public HttpDownloadService CreateClientNoSsl()
37 | {
38 | var httpClient = _clientFactory.CreateClient("NoSSL");
39 |
40 | return new HttpDownloadService(_log, httpClient, _fileSystemProvider, _versionProvider);
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/Octoshift/InsufficientPermissionsMessageGenerator.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI;
2 | public static class InsufficientPermissionsMessageGenerator
3 | {
4 | public static string Generate(string organizationLogin)
5 | {
6 | return $". Please check that:\n (a) you are a member of the `{organizationLogin}` organization,\n (b) you are an organization owner or you have been granted the migrator role and\n (c) your personal access token has the correct scopes.\nFor more information, see https://docs.github.com/en/migrations/using-github-enterprise-importer/preparing-to-migrate-with-github-enterprise-importer/managing-access-for-github-enterprise-importer.";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/AdoRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models;
2 |
3 | public record AdoRepository
4 | {
5 | public string Id { get; init; }
6 | public string Name { get; init; }
7 | public ulong? Size { get; init; }
8 | public bool IsDisabled { get; init; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/BbsRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models;
2 |
3 | public record BbsRepository
4 | {
5 | public string Id { get; init; }
6 | public string Name { get; init; }
7 | public string Slug { get; init; }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/CodeScanningAlert.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models;
2 |
3 | public class CodeScanningAlert
4 | {
5 | public int Number { get; set; }
6 | public string Url { get; set; }
7 | public string State { get; set; }
8 | public string DismissedAt { get; set; }
9 | public string DismissedReason { get; set; }
10 | public string DismissedComment { get; set; }
11 | public CodeScanningAlertInstance MostRecentInstance { get; set; }
12 | public string RuleId { get; set; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/CodeScanningAlertInstance.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models;
2 |
3 | public class CodeScanningAlertInstance
4 | {
5 | public string Ref { get; set; }
6 | public string CommitSha { get; set; }
7 | public string Path { get; set; }
8 | public int StartLine { get; set; }
9 | public int EndLine { get; set; }
10 | public int StartColumn { get; set; }
11 | public int EndColumn { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/CodeScanningAnalysis.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models
2 | {
3 | public class CodeScanningAnalysis
4 | {
5 | public string Ref { get; set; }
6 | public string CommitSha { get; set; }
7 | public string CreatedAt { get; set; }
8 | public int Id { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/CreateAttributionInvitationResult.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models
2 | {
3 | public class CreateAttributionInvitationResult : GraphqlResult
4 | {
5 | }
6 |
7 | public class CreateAttributionInvitationData
8 | {
9 | public CreateAttributionInvitation CreateAttributionInvitation { get; set; }
10 | }
11 |
12 | public class CreateAttributionInvitation
13 | {
14 | public UserInfo Source { get; set; }
15 | public UserInfo Target { get; set; }
16 | }
17 |
18 | public class UserInfo
19 | {
20 | public string Id { get; set; }
21 | public string Login { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/GithubSecretScanningAlert.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models;
2 | public class GithubSecretScanningAlert
3 | {
4 | public int Number { get; set; }
5 | public string State { get; set; }
6 | public string Resolution { get; set; }
7 | public string ResolutionComment { get; set; }
8 | public string SecretType { get; set; }
9 | public string Secret { get; set; }
10 | public string ResolverName { get; set; }
11 | }
12 |
13 | public class GithubSecretScanningAlertLocation
14 | {
15 | public string LocationType { get; set; }
16 | public string Path { get; set; }
17 | public int StartLine { get; set; }
18 | public int EndLine { get; set; }
19 | public int StartColumn { get; set; }
20 | public int EndColumn { get; set; }
21 | public string BlobSha { get; set; }
22 | public string IssueTitleUrl { get; set; }
23 | public string IssueBodyUrl { get; set; }
24 | public string IssueCommentUrl { get; set; }
25 | public string DiscussionTitleUrl { get; set; }
26 | public string DiscussionBodyUrl { get; set; }
27 | public string DiscussionCommentUrl { get; set; }
28 | public string PullRequestTitleUrl { get; set; }
29 | public string PullRequestBodyUrl { get; set; }
30 | public string PullRequestCommentUrl { get; set; }
31 | public string PullRequestReviewUrl { get; set; }
32 | public string PullRequestReviewCommentUrl { get; set; }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/GraphqlResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace Octoshift.Models
4 | {
5 | public abstract class GraphqlResult
6 | {
7 | public T Data { get; init; }
8 | public Collection Errors { get; init; }
9 | }
10 |
11 | public class ErrorData
12 | {
13 | public string Type { get; set; }
14 | public Collection Path { get; init; }
15 | public Collection Locations { get; init; }
16 | public string Message { get; set; }
17 | }
18 |
19 | public class Location
20 | {
21 | public long Line { get; init; }
22 | public long Column { get; init; }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/Mannequin.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace OctoshiftCLI.Models
3 | {
4 | public class Claimant
5 | {
6 | public string Id { get; set; }
7 | public string Login { get; set; }
8 | }
9 | public class Mannequin
10 | {
11 | public string Id { get; set; }
12 | public string Login { get; set; }
13 | public Claimant MappedUser { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/MannequinReclaimResult.cs:
--------------------------------------------------------------------------------
1 | namespace Octoshift.Models
2 | {
3 | public class ReattributeMannequinToUserResult : GraphqlResult
4 | {
5 | }
6 |
7 | public class ReattributeMannequinToUserData
8 | {
9 | public ReattributeMannequinToUser ReattributeMannequinToUser { get; set; }
10 | }
11 |
12 | public class ReattributeMannequinToUser
13 | {
14 | public UserInfo Source { get; set; }
15 | public UserInfo Target { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Octoshift/Models/SarifProcessingStatus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace OctoshiftCLI.Models;
4 |
5 | public class SarifProcessingStatus
6 | {
7 | public const string Failed = "failed";
8 | public const string Complete = "complete";
9 | public const string Pending = "pending";
10 |
11 | public string Status { get; set; }
12 | public IEnumerable Errors { get; init; }
13 |
14 | public static bool IsPending(string status) => status?.Trim().ToLower() is Pending;
15 | public static bool IsFailed(string status) => status?.Trim().ToLower() is Failed;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Octoshift/Octoshift.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | OctoshiftCLI
6 | 12
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Octoshift/OctoshiftCliException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace OctoshiftCLI
4 | {
5 | public class OctoshiftCliException : Exception
6 | {
7 | public OctoshiftCliException()
8 | {
9 | }
10 |
11 | public OctoshiftCliException(string message) : base(message)
12 | {
13 | }
14 |
15 | public OctoshiftCliException(string message, Exception innerException) : base(message, innerException)
16 | {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Octoshift/OrganizationMigrationStatus.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI
2 | {
3 | public static class OrganizationMigrationStatus
4 | {
5 | public const string Queued = "QUEUED";
6 | public const string InProgress = "IN_PROGRESS";
7 | public const string Failed = "FAILED";
8 | public const string Succeeded = "SUCCEEDED";
9 | public const string NotStarted = "NOT_STARTED";
10 | public const string PostRepoMigration = "POST_REPO_MIGRATION";
11 | public const string PreRepoMigration = "PRE_REPO_MIGRATION";
12 | public const string RepoMigration = "REPO_MIGRATION";
13 |
14 | public static bool IsSucceeded(string migrationState) => migrationState?.Trim().ToUpper() is Succeeded;
15 | public static bool IsPending(string migrationState) => migrationState?.Trim().ToUpper() is Queued or InProgress or NotStarted or PostRepoMigration or PreRepoMigration or RepoMigration;
16 | public static bool IsFailed(string migrationState) => !(IsPending(migrationState) || IsSucceeded(migrationState));
17 | public static bool IsRepoMigration(string migrationState) => migrationState?.Trim().ToUpper() is RepoMigration;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Octoshift/RepositoryMigrationStatus.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI
2 | {
3 | public static class RepositoryMigrationStatus
4 | {
5 | public const string Queued = "QUEUED";
6 | public const string InProgress = "IN_PROGRESS";
7 | public const string Failed = "FAILED";
8 | public const string Succeeded = "SUCCEEDED";
9 | public const string PendingValidation = "PENDING_VALIDATION";
10 | public const string FailedValidation = "FAILED_VALIDATION";
11 |
12 | public static bool IsSucceeded(string migrationState) => migrationState?.Trim().ToUpper() is Succeeded;
13 | public static bool IsPending(string migrationState) => migrationState?.Trim().ToUpper() is Queued or InProgress or PendingValidation;
14 | public static bool IsFailed(string migrationState) => !(IsPending(migrationState) || IsSucceeded(migrationState));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Octoshift/SecretScanningAlert.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI;
2 | public static class SecretScanningAlert
3 | {
4 | public const string AlertStateOpen = "open";
5 | public const string AlertStateResolved = "resolved";
6 |
7 | public const string ResolutionFalsePositive = "false_positive";
8 | public const string ResolutionRevoked = "revoked";
9 | public const string ResolutionWontFix = "wont_fix";
10 | public const string ResolutionUsedInTests = "used_in_tests";
11 |
12 | public static bool IsResolved(string alertState) => alertState?.Trim().ToLower() is AlertStateResolved;
13 |
14 | public static bool IsOpen(string alertState) => alertState?.Trim().ToLower() is AlertStateOpen;
15 |
16 | public static bool IsOpenOrResolved(string alertState) =>
17 | alertState?.Trim().ToLower() is AlertStateOpen or AlertStateResolved;
18 |
19 | public static bool IsValidDismissedReason(string reason) =>
20 | reason?.Trim().ToLower() is ResolutionFalsePositive or ResolutionRevoked or ResolutionWontFix or ResolutionUsedInTests;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/BasicHttpClient.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Net.Http.Headers;
3 | using System.Threading.Tasks;
4 | using OctoshiftCLI.Contracts;
5 |
6 | namespace OctoshiftCLI.Services;
7 |
8 | public class BasicHttpClient
9 | {
10 | private readonly HttpClient _httpClient;
11 | private readonly OctoLogger _log;
12 |
13 | public BasicHttpClient(OctoLogger log, HttpClient httpClient, IVersionProvider versionProvider)
14 | {
15 | _log = log;
16 | _httpClient = httpClient;
17 |
18 | if (_httpClient != null)
19 | {
20 | _httpClient.DefaultRequestHeaders.Add("accept", "application/json");
21 | _httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("OctoshiftCLI", versionProvider?.GetCurrentVersion()));
22 | if (versionProvider?.GetVersionComments() is { } comments)
23 | {
24 | _httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(comments));
25 | }
26 | }
27 | }
28 |
29 | public async Task GetAsync(string url)
30 | {
31 | _log.LogVerbose($"HTTP GET: {url}");
32 |
33 | var response = await _httpClient.GetAsync(url);
34 | var content = await response.Content.ReadAsStringAsync();
35 | _log.LogVerbose($"RESPONSE ({response.StatusCode}): {content}");
36 |
37 | response.EnsureSuccessStatusCode();
38 |
39 | return content;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/DateTimeProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace OctoshiftCLI.Services;
4 |
5 | public class DateTimeProvider
6 | {
7 | public virtual long CurrentUnixTimeSeconds() => DateTimeOffset.Now.ToUnixTimeSeconds();
8 | }
9 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/FileSystemProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace OctoshiftCLI.Services;
7 |
8 | public class FileSystemProvider
9 | {
10 | public virtual bool FileExists(string path) => File.Exists(path);
11 |
12 | public virtual Task ReadAllBytesAsync(string path) => File.ReadAllBytesAsync(path);
13 |
14 | public virtual DirectoryInfo CreateDirectory(string path) => Directory.CreateDirectory(path);
15 |
16 | public virtual FileStream Open(string path, FileMode mode) => File.Open(path, mode);
17 |
18 | public virtual Stream OpenRead(string path) => File.OpenRead(path);
19 |
20 | public virtual async Task WriteAllTextAsync(string path, string contents) => await File.WriteAllTextAsync(path, contents);
21 |
22 | public virtual async ValueTask WriteAsync(FileStream fileStream, ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
23 | {
24 | if (fileStream is null)
25 | {
26 | return;
27 | }
28 |
29 | await fileStream.WriteAsync(buffer, cancellationToken);
30 | }
31 |
32 | public virtual void DeleteIfExists(string path)
33 | {
34 | if (File.Exists(path))
35 | {
36 | File.Delete(path);
37 | }
38 | }
39 |
40 | public virtual string GetTempFileName() => Path.GetTempFileName();
41 |
42 | public virtual async Task CopySourceToTargetStreamAsync(Stream source, Stream target)
43 | {
44 | if (source != null)
45 | {
46 | await source.CopyToAsync(target);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/GenericArgsBinder.cs:
--------------------------------------------------------------------------------
1 | using System.CommandLine;
2 | using System.CommandLine.Binding;
3 | using System.Linq;
4 |
5 | namespace OctoshiftCLI.Services;
6 |
7 | public class GenericArgsBinder : BinderBase
8 | where TCommand : notnull
9 | where TArgs : class, new()
10 | {
11 | private readonly TCommand _command;
12 |
13 | public GenericArgsBinder(TCommand command) => _command = command;
14 |
15 | protected override TArgs GetBoundValue(BindingContext bindingContext)
16 | {
17 | var args = new TArgs();
18 |
19 | foreach (var prop in typeof(TCommand).GetProperties().Where(p => p.PropertyType.IsAssignableTo(typeof(Option))))
20 | {
21 | typeof(TArgs)
22 | .GetProperty(prop.Name)?
23 | .SetValue(args, bindingContext?.ParseResult.GetValueForOption((Option)prop.GetValue(_command)!));
24 | }
25 |
26 | return args;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/GithubStatusApi.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Newtonsoft.Json.Linq;
4 |
5 | namespace OctoshiftCLI.Services;
6 |
7 | public class GithubStatusApi
8 | {
9 | private readonly BasicHttpClient _client;
10 | private const string GITHUB_STATUS_API_URL = "https://www.githubstatus.com/api/v2";
11 |
12 | public GithubStatusApi(BasicHttpClient client)
13 | {
14 | _client = client;
15 | }
16 |
17 | public virtual async Task GetUnresolvedIncidentsCount()
18 | {
19 | var url = $"{GITHUB_STATUS_API_URL}/incidents/unresolved.json";
20 | var response = await _client.GetAsync(url);
21 |
22 | return JObject.Parse(response)["incidents"].Count();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/StringCompressor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.IO.Compression;
4 | using System.Text;
5 |
6 | namespace OctoshiftCLI.Services;
7 |
8 | public static class StringCompressor
9 | {
10 | // Zip Solution taken from
11 | // https://stackoverflow.com/questions/7343465/compression-decompression-string-with-c-sharp
12 | public static string GZipAndBase64String(string str)
13 | {
14 | var bytes = Encoding.UTF8.GetBytes(str);
15 |
16 | using var msi = new MemoryStream(bytes);
17 | using var mso = new MemoryStream();
18 | using (var gs = new GZipStream(mso, CompressionMode.Compress))
19 | {
20 | msi.CopyTo(gs);
21 | }
22 |
23 | return Convert.ToBase64String(mso.ToArray());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Octoshift/Services/WarningsCountLogger.cs:
--------------------------------------------------------------------------------
1 | namespace OctoshiftCLI.Services;
2 |
3 | public class WarningsCountLogger
4 | {
5 | private readonly OctoLogger _log;
6 |
7 | public WarningsCountLogger(OctoLogger logger)
8 | {
9 | _log = logger;
10 | }
11 |
12 | public void LogWarningsCount(int warningsCount)
13 | {
14 | switch (warningsCount)
15 | {
16 | case 0:
17 | break;
18 | case 1:
19 | _log.LogWarning("1 warning encountered during this migration");
20 | break;
21 | default:
22 | _log.LogWarning($"{warningsCount} warnings encountered during this migration");
23 | break;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.IntegrationTests/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CA1707: Identifiers should not contain underscores
4 | dotnet_diagnostic.CA1707.severity = none
5 |
6 | # Naming rules
7 |
8 | dotnet_naming_rule.public_methods_should_be_snake_case.severity = suggestion
9 | dotnet_naming_rule.public_methods_should_be_snake_case.symbols = public_methods
10 | dotnet_naming_rule.public_methods_should_be_snake_case.style = snake_case
11 |
12 | # Symbol specifications
13 |
14 | dotnet_naming_symbols.public_methods.applicable_kinds = method
15 | dotnet_naming_symbols.public_methods.applicable_accessibilities = public
16 |
17 | # Naming styles
18 |
19 | dotnet_naming_style.snake_case.capitalization = pascal_case
20 | dotnet_naming_style.snake_case.word_separator = _
21 | dotnet_naming_style.snake_case.required_prefix =
22 | dotnet_naming_style.snake_case.required_suffix =
--------------------------------------------------------------------------------
/src/OctoshiftCLI.IntegrationTests/OctoshiftCLI.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | 12
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 | all
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | PreserveNewest
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.IntegrationTests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "showLiveOutput": true
4 | }
5 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # CA1707: Identifiers should not contain underscores
4 | dotnet_diagnostic.CA1707.severity = none
5 |
6 | # Naming rules
7 |
8 | dotnet_naming_rule.public_methods_should_be_snake_case.severity = suggestion
9 | dotnet_naming_rule.public_methods_should_be_snake_case.symbols = public_methods
10 | dotnet_naming_rule.public_methods_should_be_snake_case.style = snake_case
11 |
12 | # Symbol specifications
13 |
14 | dotnet_naming_symbols.public_methods.applicable_kinds = method
15 | dotnet_naming_symbols.public_methods.applicable_accessibilities = public
16 |
17 | # Naming styles
18 |
19 | dotnet_naming_style.snake_case.capitalization = pascal_case
20 | dotnet_naming_style.snake_case.word_separator = _
21 | dotnet_naming_style.snake_case.required_prefix =
22 | dotnet_naming_style.snake_case.required_suffix =
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/InsufficientPermissionsMessageGeneratorTest.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace OctoshiftCLI.Tests
5 | {
6 | public class InsufficientPermissionsMessageGeneratorTest
7 | {
8 | [Fact]
9 | public void Generate_Returns_Message_With_Interpolated_Login()
10 | {
11 | InsufficientPermissionsMessageGenerator.Generate("monalisa-corp").Should().Be(". Please check that:\n (a) you are a member of the `monalisa-corp` organization,\n (b) you are an organization owner or you have been granted the migrator role and\n (c) your personal access token has the correct scopes.\nFor more information, see https://docs.github.com/en/migrations/using-github-enterprise-importer/preparing-to-migrate-with-github-enterprise-importer/managing-access-for-github-enterprise-importer.");
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/AbortMigration/AbortMigrationCommandBaseTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Moq;
3 | using OctoshiftCLI.Commands.AbortMigration;
4 | using OctoshiftCLI.Contracts;
5 | using OctoshiftCLI.Services;
6 | using Xunit;
7 |
8 | namespace OctoshiftCLI.Tests.Octoshift.Commands.AbortMigration;
9 |
10 | public class AbortMigrationCommandBaseTests
11 | {
12 | private readonly Mock _mockGithubApiFactory = new();
13 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
14 | private readonly ServiceProvider _serviceProvider;
15 | private readonly AbortMigrationCommandBase _command = [];
16 |
17 | private const string REPO_MIGRATION_ID = "RM_MIGRATION_ID";
18 |
19 | public AbortMigrationCommandBaseTests()
20 | {
21 | var serviceCollection = new ServiceCollection();
22 | serviceCollection
23 | .AddSingleton(_mockOctoLogger.Object)
24 | .AddSingleton(_mockGithubApiFactory.Object);
25 |
26 | _serviceProvider = serviceCollection.BuildServiceProvider();
27 | }
28 |
29 | [Fact]
30 | public void It_Uses_The_TargetApiUrl_When_Provided()
31 | {
32 | var targetApiUrl = "TargetApiUrl";
33 |
34 | var args = new AbortMigrationCommandArgs
35 | {
36 | MigrationId = REPO_MIGRATION_ID,
37 | TargetApiUrl = targetApiUrl
38 | };
39 |
40 | _command.BuildHandler(args, _serviceProvider);
41 |
42 | _mockGithubApiFactory.Verify(m => m.Create(targetApiUrl, It.IsAny()));
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandBaseTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Moq;
3 | using OctoshiftCLI.Commands.GenerateMannequinCsv;
4 | using OctoshiftCLI.Contracts;
5 | using OctoshiftCLI.Services;
6 | using Xunit;
7 |
8 | namespace OctoshiftCLI.Tests.Octoshift.Commands.GeneerateMannequinCsv;
9 |
10 | public class GenerateMannequinCsvCommandBaseTests
11 | {
12 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
13 | private readonly Mock _mockGithubApiFactory = new();
14 | private readonly ServiceProvider _serviceProvider;
15 | private readonly GenerateMannequinCsvCommandBase _command = [];
16 | private const string GITHUB_ORG = "FooOrg";
17 |
18 | public GenerateMannequinCsvCommandBaseTests()
19 | {
20 | var serviceCollection = new ServiceCollection();
21 | serviceCollection
22 | .AddSingleton(_mockOctoLogger.Object)
23 | .AddSingleton(_mockGithubApiFactory.Object);
24 |
25 | _serviceProvider = serviceCollection.BuildServiceProvider();
26 | }
27 |
28 | [Fact]
29 | public void It_Uses_The_TargetApiUrl_When_Provided()
30 | {
31 | var targetApiUrl = "TARGET-API-URL";
32 |
33 | var args = new GenerateMannequinCsvCommandArgs
34 | {
35 | GithubOrg = GITHUB_ORG,
36 | IncludeReclaimed = false,
37 | TargetApiUrl = targetApiUrl,
38 | };
39 |
40 | _command.BuildHandler(args, _serviceProvider);
41 |
42 | _mockGithubApiFactory.Verify(m => m.Create(targetApiUrl, null));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandArgsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Moq;
3 | using OctoshiftCLI.Commands.GrantMigratorRole;
4 | using OctoshiftCLI.Services;
5 | using Xunit;
6 |
7 | namespace OctoshiftCLI.Tests.Octoshift.Commands.GrantMigratorRole;
8 |
9 | public class GrantMigratorRoleCommandArgsTests
10 | {
11 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
12 |
13 | private const string GITHUB_ORG = "FooOrg";
14 | private const string ACTOR = "foo-actor";
15 |
16 | [Fact]
17 | public void Invalid_Actor_Type()
18 | {
19 | var args = new GrantMigratorRoleCommandArgs
20 | {
21 | GithubOrg = GITHUB_ORG,
22 | Actor = ACTOR,
23 | ActorType = "INVALID",
24 | };
25 |
26 | FluentActions.Invoking(() => args.Validate(_mockOctoLogger.Object))
27 | .Should().Throw();
28 | }
29 |
30 | [Fact]
31 | public void It_Validates_GhesApiUrl_And_TargetApiUrl()
32 | {
33 | var args = new GrantMigratorRoleCommandArgs
34 | {
35 | GithubOrg = GITHUB_ORG,
36 | Actor = ACTOR,
37 | ActorType = "USER",
38 | GhesApiUrl = "https://ghes.example.com",
39 | TargetApiUrl = "https://api.github.com",
40 | };
41 |
42 | FluentActions.Invoking(() => args.Validate(_mockOctoLogger.Object))
43 | .Should().Throw();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandHandlerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Moq;
4 | using OctoshiftCLI.Commands.GrantMigratorRole;
5 | using OctoshiftCLI.Services;
6 | using Xunit;
7 |
8 | namespace OctoshiftCLI.Tests.Octoshift.Commands.GrantMigratorRole;
9 |
10 | public class GrantMigratorRoleCommandHandlerTests
11 | {
12 | private readonly Mock _mockGithubApi = TestHelpers.CreateMock();
13 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
14 |
15 | private readonly GrantMigratorRoleCommandHandler _handler;
16 |
17 | private const string GITHUB_ORG = "FooOrg";
18 | private const string ACTOR = "foo-actor";
19 | private const string ACTOR_TYPE = "TEAM";
20 |
21 | public GrantMigratorRoleCommandHandlerTests()
22 | {
23 | _handler = new GrantMigratorRoleCommandHandler(_mockOctoLogger.Object, _mockGithubApi.Object);
24 | }
25 |
26 | [Fact]
27 | public async Task Happy_Path()
28 | {
29 | var githubOrgId = Guid.NewGuid().ToString();
30 |
31 | _mockGithubApi.Setup(x => x.GetOrganizationId(GITHUB_ORG).Result).Returns(githubOrgId);
32 |
33 | var args = new GrantMigratorRoleCommandArgs
34 | {
35 | GithubOrg = GITHUB_ORG,
36 | Actor = ACTOR,
37 | ActorType = ACTOR_TYPE,
38 | };
39 | await _handler.Handle(args);
40 |
41 | _mockGithubApi.Verify(x => x.GrantMigratorRole(githubOrgId, ACTOR, ACTOR_TYPE));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandArgsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Moq;
3 | using OctoshiftCLI.Commands.ReclaimMannequin;
4 | using OctoshiftCLI.Services;
5 | using Xunit;
6 |
7 | namespace OctoshiftCLI.Tests.Octoshift.Commands.ReclaimMannequin;
8 |
9 | public class ReclaimMannequinCommandArgsTests
10 | {
11 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
12 |
13 | private const string GITHUB_ORG = "FooOrg";
14 |
15 | [Fact]
16 | public void No_Parameters_Provided_Throws_OctoshiftCliException()
17 | {
18 | var args = new ReclaimMannequinCommandArgs { GithubOrg = GITHUB_ORG };
19 |
20 | FluentActions
21 | .Invoking(() => args.Validate(_mockOctoLogger.Object))
22 | .Should().Throw();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/RevokeMigratorRole/RevokeMigratorRoleCommandArgsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Moq;
3 | using OctoshiftCLI.Commands.RevokeMigratorRole;
4 | using OctoshiftCLI.Services;
5 | using Xunit;
6 |
7 | namespace OctoshiftCLI.Tests.Octoshift.Commands.RevokeMigratorRole;
8 |
9 | public class RevokeMigratorRoleCommandArgsTests
10 | {
11 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
12 |
13 | private const string GITHUB_ORG = "FooOrg";
14 | private const string ACTOR = "foo-actor";
15 |
16 | [Fact]
17 | public void Invalid_Actor_Type()
18 | {
19 | var args = new RevokeMigratorRoleCommandArgs
20 | {
21 | GithubOrg = GITHUB_ORG,
22 | Actor = ACTOR,
23 | ActorType = "INVALID",
24 | };
25 |
26 | FluentActions
27 | .Invoking(() => args.Validate(_mockOctoLogger.Object))
28 | .Should()
29 | .ThrowExactly();
30 | }
31 | [Fact]
32 | public void It_Validates_GhesApiUrl_And_TargetApiUrl()
33 | {
34 | var args = new RevokeMigratorRoleCommandArgs
35 | {
36 | GithubOrg = GITHUB_ORG,
37 | Actor = ACTOR,
38 | ActorType = "USER",
39 | GhesApiUrl = "https://ghes.example.com",
40 | TargetApiUrl = "https://api.github.com",
41 | };
42 |
43 | FluentActions
44 | .Invoking(() => args.Validate(_mockOctoLogger.Object))
45 | .Should()
46 | .ThrowExactly();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/RevokeMigratorRole/RevokeMigratorRoleCommandHandlerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Moq;
4 | using OctoshiftCLI.Commands.RevokeMigratorRole;
5 | using OctoshiftCLI.Services;
6 | using Xunit;
7 |
8 | namespace OctoshiftCLI.Tests.Octoshift.Commands.RevokeMigratorRole;
9 |
10 | public class RevokeMigratorRoleCommandHandlerTests
11 | {
12 | private readonly Mock _mockGithubApi = TestHelpers.CreateMock();
13 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
14 |
15 | private readonly RevokeMigratorRoleCommandHandler _handler;
16 |
17 | private const string GITHUB_ORG = "FooOrg";
18 | private const string ACTOR = "foo-actor";
19 | private const string ACTOR_TYPE = "TEAM";
20 |
21 | public RevokeMigratorRoleCommandHandlerTests()
22 | {
23 | _handler = new RevokeMigratorRoleCommandHandler(_mockOctoLogger.Object, _mockGithubApi.Object);
24 | }
25 |
26 | [Fact]
27 | public async Task Happy_Path()
28 | {
29 | var githubOrgId = Guid.NewGuid().ToString();
30 |
31 | _mockGithubApi.Setup(x => x.GetOrganizationId(GITHUB_ORG).Result).Returns(githubOrgId);
32 |
33 | var args = new RevokeMigratorRoleCommandArgs
34 | {
35 | GithubOrg = GITHUB_ORG,
36 | Actor = ACTOR,
37 | ActorType = ACTOR_TYPE,
38 | };
39 | await _handler.Handle(args);
40 |
41 | _mockGithubApi.Verify(x => x.RevokeMigratorRole(githubOrgId, ACTOR, ACTOR_TYPE));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Commands/WaitForMigration/WaitForMigrationCommandArgsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Moq;
3 | using OctoshiftCLI.Commands.WaitForMigration;
4 | using OctoshiftCLI.Services;
5 | using Xunit;
6 |
7 | namespace OctoshiftCLI.Tests.Octoshift.Commands.WaitForMigration;
8 |
9 | public class WaitForMigrationCommandArgsTests
10 | {
11 | private readonly Mock _mockOctoLogger = TestHelpers.CreateMock();
12 |
13 | [Fact]
14 | public void With_Invalid_Migration_ID_Prefix_Throws_Exception()
15 | {
16 | var invalidId = "SomeId";
17 |
18 | var args = new WaitForMigrationCommandArgs
19 | {
20 | MigrationId = invalidId,
21 | };
22 |
23 | FluentActions
24 | .Invoking(() => args.Validate(_mockOctoLogger.Object))
25 | .Should()
26 | .Throw()
27 | .WithMessage($"Invalid migration id: {invalidId}");
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Services/StringCompressorTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using OctoshiftCLI.Services;
3 | using Xunit;
4 |
5 | namespace OctoshiftCLI.Tests.Octoshift.Services;
6 |
7 | public class StringCompressorTests
8 | {
9 | [Fact]
10 | public void GZipAndBase64String_Correctly_Compresses_String()
11 | {
12 | var uncompressed = "uncompressed_test_string";
13 | // It seems like, depending on the underlying OS, two characters in the Zipped and Base64 encoded string differ.
14 | // So we simply check for all variants here to cover Linux, Mac OSX and Windows.
15 | var expectedStringPattern = "H4sIAAAAAAAA(Ey|Ci|Ay)vNS87PLShKLS5OTYkvSS0uiS8uKcrMSwcAdqGS8xgAAAA=";
16 |
17 | var actualString = StringCompressor.GZipAndBase64String(uncompressed);
18 |
19 | actualString.Should().MatchRegex(expectedStringPattern);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Services/VersionCheckerTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using OctoshiftCLI.Services;
3 | using Xunit;
4 |
5 | namespace OctoshiftCLI.Tests.Octoshift.Services;
6 |
7 | public class VersionCheckerTests
8 | {
9 | [Fact]
10 | public void GetVersionComments_Returns_Root_And_Executing_Commands()
11 | {
12 | // Arrange
13 | TestHelpers.SetCliContext();
14 |
15 | var versionChecker = new VersionChecker(null, null);
16 |
17 | // Act
18 | var comments = versionChecker.GetVersionComments();
19 |
20 | // Assert
21 | comments.Should().Be($"({TestHelpers.CLI_ROOT_COMMAND}/{TestHelpers.CLI_EXECUTING_COMMAND})");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/Octoshift/Services/WarningsCountLoggerTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using OctoshiftCLI.Services;
3 | using Xunit;
4 |
5 | namespace OctoshiftCLI.Tests.Octoshift.Services;
6 |
7 | public class WarningsCountLoggerTests
8 | {
9 | private string _logOutput;
10 | private string _verboseLogOutput;
11 | private string _consoleOutput;
12 | private string _consoleError;
13 |
14 | private readonly OctoLogger _octoLogger;
15 | private readonly WarningsCountLogger _warningsCountLogger;
16 |
17 | public WarningsCountLoggerTests()
18 | {
19 | _octoLogger = new OctoLogger(CaptureLogOutput, CaptureVerboseLogOutput, CaptureConsoleOutput, CaptureConsoleError);
20 | _warningsCountLogger = new WarningsCountLogger(_octoLogger);
21 | }
22 |
23 | [Fact]
24 | public void LogWarningsCount_Should_Write_1_Warning_To_Console_Out()
25 | {
26 | _warningsCountLogger.LogWarningsCount(1);
27 |
28 | _consoleOutput.Should().Contain("1 warning encountered during this migration");
29 | }
30 |
31 | [Fact]
32 | public void LogWarningsCount_Should_Write_Warnings_Count_To_Console_Out()
33 | {
34 | _warningsCountLogger.LogWarningsCount(3);
35 |
36 | _consoleOutput.Should().Contain($"3 warnings encountered during this migration");
37 | }
38 |
39 | private void CaptureLogOutput(string msg) => _logOutput += msg;
40 |
41 | private void CaptureVerboseLogOutput(string msg) => _verboseLogOutput += msg;
42 |
43 | private void CaptureConsoleOutput(string msg) => _consoleOutput += msg;
44 |
45 | private void CaptureConsoleError(string msg) => _consoleError += msg;
46 | }
47 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/OctoshiftCLI.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | false
6 | 12
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 | all
28 |
29 |
30 | runtime; build; native; contentfiles; analyzers; buildtransitive
31 | all
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/StringExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using OctoshiftCLI.Extensions;
3 | using Xunit;
4 |
5 | namespace OctoshiftCLI.Tests
6 | {
7 | public sealed class StringExtensionsTests
8 | {
9 | [Theory]
10 | [InlineData(null, "")]
11 | [InlineData("Parts Unlimited", "Parts-Unlimited")]
12 | [InlineData("Parts-Unlimited", "Parts-Unlimited")]
13 | [InlineData("Parts@@@@Unlimited", "Parts-Unlimited")]
14 | public void ReplaceInvalidCharactersWithDash_Returns_Valid_String(string value, string expectedValue)
15 | {
16 | var normalizedValue = value.ReplaceInvalidCharactersWithDash();
17 |
18 | normalizedValue.Should().Be(expectedValue);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/TestExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 | using Newtonsoft.Json.Linq;
6 |
7 | namespace OctoshiftCLI.Tests
8 | {
9 | public static class TestExtensionMethods
10 | {
11 | public static IAsyncEnumerable ToAsyncJTokenEnumerable(this IEnumerable list)
12 | => list.Select(x => JToken.FromObject(x)).ToAsyncEnumerable();
13 |
14 | public static MemoryStream ToStream(this string value)
15 | {
16 | return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/OctoshiftCLI.Tests/TestHelpers.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.CommandLine;
3 | using System.Linq;
4 | using Moq;
5 | using Xunit;
6 |
7 | namespace OctoshiftCLI.Tests
8 | {
9 | public static class TestHelpers
10 | {
11 | private static readonly object _mutex = new();
12 |
13 | public const string CLI_ROOT_COMMAND = "ROOT_COMMAND";
14 | public const string CLI_EXECUTING_COMMAND = "EXECUTING_COMMAND";
15 |
16 | public static Mock CreateMock() where T : class
17 | {
18 | var ctor = typeof(T).GetConstructors().First();
19 | var argCount = ctor.GetParameters().Length;
20 | var args = new object[argCount];
21 |
22 | return new Mock(args);
23 | }
24 |
25 | public static void VerifyCommandOption(IReadOnlyList