Description
gh gl2gh migrate-repo throws an unhandled NullReferenceException when invoked with --archive-path (importing an existing archive) and no --gitlab-server-url. This is the documented "import-only" flow for archives produced out-of-band (e.g. via the legacy gl-exporter Docker tool, or in environments where the GitLab server is not reachable from the migration host).
Reproduced against a GHE.com Data Residency tenant (api.<tenant>.ghe.com), but the bug is not specific to Data Residency — it's a null-deref in MigrateRepoCommandHandler.Handle that happens before any GitHub call.
Version
gl2gh v1.30.0 (first release containing the adapter from #1555)
Environment
- Target: GHE.com Data Residency (
https://api.<tenant>.ghe.com)
- Host OS: Linux (WSL2 on Windows) — also reproduces on macOS
gh CLI: latest stable
- Archive: produced by the standalone
gl-exporter Docker image, single .tar.gz
Repro
gh gl2gh migrate-repo \
--github-pat *** \
--github-org my-org \
--github-repo my-repo \
--target-api-url https://api.<tenant>.ghe.com \
--archive-path /path/to/archive.tar.gz \
--use-github-storage \
--target-repo-visibility private \
--keep-archive \
--verbose
Actual output
[INFO] You are running an up-to-date version of the gl2gh extension [v1.30.0]
[INFO] ARCHIVE PATH: /path/to/archive.tar.gz
[INFO] GITHUB ORG: my-org
[INFO] GITHUB REPO: my-repo
[INFO] GITHUB PAT: ***
[INFO] TARGET REPO VISIBILITY: private
[INFO] TARGET API URL: https://api.<tenant>.ghe.com
[INFO] KEEP ARCHIVE: true
[INFO] USE GITHUB STORAGE: true
[INFO] VERBOSE: true
[ERROR] System.NullReferenceException: Object reference not set to an instance of an object.
at OctoshiftCLI.GitlabToGithub.Commands.MigrateRepo.MigrateRepoCommandHandler.Handle(MigrateRepoCommandArgs args)
at OctoshiftCLI.Extensions.CommandExtensions.RunHandler[TArgs,THandler](TArgs args, ServiceProvider sp, CommandBase`2 command)
Expected behavior
When --archive-path is provided without --gitlab-server-url, the CLI should skip any interaction with GitLab and proceed directly to the upload + import phases (consistent with MigrateRepoCommandArgs.ShouldGenerateArchive() returning false in this case).
Root cause
MigrateRepoCommand.BuildHandler only constructs GitlabApi when --gitlab-server-url is provided:
GitlabApi gitlabApi = null;
...
if (args.GitlabServerUrl.HasValue())
{
var gitlabApiFactory = sp.GetRequiredService<GitlabApiFactory>();
gitlabApi = gitlabApiFactory.Create(args.GitlabServerUrl, args.GitlabPat, args.NoSslVerify);
}
(src/gl2gh/Commands/MigrateRepo/MigrateRepoCommand.cs)
But MigrateRepoCommandHandler.Handle dereferences _gitlabApi unconditionally, right after ValidateOptions:
public async Task Handle(MigrateRepoCommandArgs args)
{
if (args is null) { throw new ArgumentNullException(nameof(args)); }
ValidateOptions(args);
await _gitlabApi.LogServerVersion(); // ← NRE in the archive-path-only flow
...
}
(src/gl2gh/Commands/MigrateRepo/MigrateRepoCommandHandler.cs)
Suggested fix
Guard the LogServerVersion() call so it only runs when a GitLab server is actually involved:
if (_gitlabApi != null)
{
await _gitlabApi.LogServerVersion();
}
Or, equivalently, gate it on args.ShouldGenerateArchive() to mirror the construction logic in BuildHandler.
Workaround
Until fixed, users with a pre-existing archive (--archive-path) must fall back to the manual GraphQL flow (createMigrationSource + startRepositoryMigration), or re-export the archive through gl2gh itself by also passing --gitlab-server-url, --gitlab-group, --gitlab-project, --gitlab-pat. Neither workaround is acceptable for users migrating from environments where the GitLab server is not reachable from the migration host, or who already have a valid archive from a separate gl-exporter run.
Impact
This blocks the documented "bring-your-own-archive" import scenario, which is the primary path for:
- Air-gapped or restricted networks where the migration host cannot reach GitLab directly
- Archives produced by the legacy
gl-exporter Docker tool that need to be imported as-is
- Re-running an import after a transient failure without re-exporting from GitLab
- Customers on GHE.com Data Residency who often run the migration host in a separate network from their GitLab instance
Description
gh gl2gh migrate-repothrows an unhandledNullReferenceExceptionwhen invoked with--archive-path(importing an existing archive) and no--gitlab-server-url. This is the documented "import-only" flow for archives produced out-of-band (e.g. via the legacygl-exporterDocker tool, or in environments where the GitLab server is not reachable from the migration host).Reproduced against a GHE.com Data Residency tenant (
api.<tenant>.ghe.com), but the bug is not specific to Data Residency — it's a null-deref inMigrateRepoCommandHandler.Handlethat happens before any GitHub call.Version
gl2ghv1.30.0 (first release containing the adapter from #1555)Environment
https://api.<tenant>.ghe.com)ghCLI: latest stablegl-exporterDocker image, single.tar.gzRepro
Actual output
Expected behavior
When
--archive-pathis provided without--gitlab-server-url, the CLI should skip any interaction with GitLab and proceed directly to the upload + import phases (consistent withMigrateRepoCommandArgs.ShouldGenerateArchive()returningfalsein this case).Root cause
MigrateRepoCommand.BuildHandleronly constructsGitlabApiwhen--gitlab-server-urlis provided:(
src/gl2gh/Commands/MigrateRepo/MigrateRepoCommand.cs)But
MigrateRepoCommandHandler.Handledereferences_gitlabApiunconditionally, right afterValidateOptions:(
src/gl2gh/Commands/MigrateRepo/MigrateRepoCommandHandler.cs)Suggested fix
Guard the
LogServerVersion()call so it only runs when a GitLab server is actually involved:Or, equivalently, gate it on
args.ShouldGenerateArchive()to mirror the construction logic inBuildHandler.Workaround
Until fixed, users with a pre-existing archive (
--archive-path) must fall back to the manual GraphQL flow (createMigrationSource+startRepositoryMigration), or re-export the archive throughgl2ghitself by also passing--gitlab-server-url,--gitlab-group,--gitlab-project,--gitlab-pat. Neither workaround is acceptable for users migrating from environments where the GitLab server is not reachable from the migration host, or who already have a valid archive from a separategl-exporterrun.Impact
This blocks the documented "bring-your-own-archive" import scenario, which is the primary path for:
gl-exporterDocker tool that need to be imported as-is