Fix order-dependent epic import gap

What does this MR do and why?

Fix order-dependent epic import gap

Imported epics are built through Gitlab::ImportExport::Base::ObjectBuilder, which caches the instance it builds in the request store for the lifetime of the Sidekiq job. For epics the builder deliberately returns an unsaved instance, because the epic work item and its synced epic are persisted separately by CreateFromImportedEpicService. That means the cached instance is never the record that gets saved, so it stays a new record for the whole job.

Every later reference to the same epic therefore tried to create it a second time, violating the unique index on issues (namespace_id, iid). BulkImports::Pipeline::Runner records that as a soft failure and moves on, so the import reported success while silently dropping every sub-epic under a shared parent and every issue-to-epic link after the first. It reproduces whenever two epics share a parent that is not imported before them, or two issues reference the same epic.

Point the built instance at the row that was persisted, so the three new_record? guards in EpicObjectCreator see the epic that already exists instead of creating another. This keeps the existing find-or-create semantics: an unrelated epic already occupying that iid still fails loudly rather than being silently reused.

Marking the instance persisted also lets NdjsonPipeline#push_placeholder_references see it, which stops user references on epics created through a nested relation from being dropped.

The regression specs set :request_store because the builder only caches while Gitlab::SafeRequestStore is active, which is true in Sidekiq but false in specs by default.

Changelog: fixed EE: true

References

#602517 (closed)

How to set up and validate locally

Note that the order in which you create these objects is important for the reproduction.

Setting up test data

  1. Create a group GroupA.
  2. Within GroupA, create ChildEpicA and ChildEpicB.
  3. Within GroupA, create ParentEpic and WorkItemParentEpic.
  4. Set ChildEpicA and ChildEpicB's parents as ParentEpic.
  5. Within GroupA, create ProjectA.
  6. Within ProjectA, create WorkItemA and WorkItemB.
  7. Set WorkItemA and WorkItemB's parent as WorkItemParentEpic.
  8. Create GroupDestBefore, GroupDestAfter, ProjectDestBefore and ProjectDestAfter.

CleanShot_2026-08-04_at_21.36.36_2x

Witness the breakage

  1. Be on master.
  2. Import GroupA into GroupDestBefore.
  3. Import ProjectA into ProjectDestBefore
curl -s --request POST \
  --url "http://gdk.test:3000/api/v4/bulk_imports" \
  --header "content-type: application/json" \
  --header "PRIVATE-TOKEN: $GITLAB_DEV_TOKEN" \
  --data "{
    \"configuration\": {
      \"url\": \"http://gdk.test:3000\",
      \"access_token\": \"$GITLAB_DEV_TOKEN\"
    },
    \"entities\": [
      {
        \"source_full_path\": \"GroupA\",
        \"source_type\": \"group_entity\",
        \"destination_slug\": \"test-001\",
        \"destination_namespace\": \"GroupDestBefore\",
        \"migrate_projects\": true
      }
    ]
  }"

curl -s --request POST \
  --url "http://gdk.test:3000/api/v4/bulk_imports" \
  --header "content-type: application/json" \
  --header "PRIVATE-TOKEN: $GITLAB_DEV_TOKEN" \
  --data "{
    \"configuration\": {
      \"url\": \"http://gdk.test:3000\",
      \"access_token\": \"$GITLAB_DEV_TOKEN\"
    },
    \"entities\": [
      {
        \"source_full_path\": \"GroupA/ProjectA\",
        \"source_type\": \"project_entity\",
        \"destination_slug\": \"project-001\",
        \"destination_namespace\": \"ProjectDestBefore\",
        \"migrate_projects\": true
      }
    ]
  }"

Witness the errors in the logs:

cat log/importer.log | grep create_from_imported_epic_service

Witness the missing records:

CleanShot_2026-08-04_at_21.38.40_2x

CleanShot_2026-08-04_at_21.37.22_2x

Repeat the imports into GroupDestAfter and ProjectDestAfter on this branch (after restarting Sidekiq) and witness the errorless logs and the properly created records.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Merge request reports

Loading
Loading