Skip to content

gl2gh migrate-repo: NullReferenceException when using --archive-path without --gitlab-server-url #1583

Description

@guigui42

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions