Skip to content

Commit 3a8ae43

Browse files
committed
fix(restore): forward -project to GitLab Name option
Without an explicit Name, GitLab populates the project's display name from the archive metadata, which leaks the source project's name into the target namespace and triggers 'Name has already been taken' even when the path is unique. Pass projectPath as Name so display name and path both follow the -project CLI flag. Closes #358
1 parent 06908e1 commit 3a8ae43

2 files changed

Lines changed: 20 additions & 4 deletions

File tree

pkg/gitlab/restore.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,18 @@ func (s *ImportService) ImportProject(
8686
return nil, fmt.Errorf("rate limit wait failed: %w", err)
8787
}
8888

89-
// Initiate import (with context support)
89+
// Initiate import. We set both Path and Name to projectPath so the display
90+
// name reflects the user-provided -project flag instead of leaking the
91+
// original project name from the archive metadata (which would otherwise
92+
// trigger "Name has already been taken" when the archive's source project
93+
// name collides with another project in the target namespace).
9094
importStatus, _, err := s.importExportService.ImportFromFile(
9195
ctx,
9296
archive,
9397
&gitlabapi.ImportFileOptions{
9498
Namespace: &namespace,
9599
Path: &projectPath,
100+
Name: &projectPath,
96101
},
97102
gitlabapi.WithContext(ctx),
98103
)

pkg/gitlab/restore_test.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ func TestImportProject(t *testing.T) {
1919
ctx := context.Background()
2020

2121
t.Run("SuccessfulImport", func(t *testing.T) {
22-
// Setup mocks
22+
// Capture the options passed to ImportFromFile so we can assert on them.
23+
var capturedOpts *gitlabapi.ImportFileOptions
2324
mockProjectImportExport := &mocks.ProjectImportExportServiceMock{
2425
ImportFromFileFunc: func(_ context.Context, archive io.Reader, opt *gitlabapi.ImportFileOptions, options ...gitlabapi.RequestOptionFunc) (*gitlabapi.ImportStatus, *gitlabapi.Response, error) {
25-
// Return successful import initiation
26+
capturedOpts = opt
2627
return &gitlabapi.ImportStatus{
2728
ID: 123,
2829
ImportStatus: "scheduled",
2930
ImportError: "",
3031
}, &gitlabapi.Response{}, nil
3132
},
3233
ImportStatusFunc: func(_ context.Context, pid any, options ...gitlabapi.RequestOptionFunc) (*gitlabapi.ImportStatus, *gitlabapi.Response, error) {
33-
// Return finished import status
3434
return &gitlabapi.ImportStatus{
3535
ID: 123,
3636
ImportStatus: "finished",
@@ -52,6 +52,17 @@ func TestImportProject(t *testing.T) {
5252
require.NotNil(t, status, "Status should not be nil")
5353
assert.Equal(t, int64(123), status.ID, "Project ID should match")
5454
assert.Equal(t, "finished", status.ImportStatus, "Import should be finished")
55+
56+
// The user-provided projectPath must be forwarded as both Path and Name,
57+
// otherwise GitLab uses the archive's original project name and may
58+
// reject the import with "Name has already been taken".
59+
require.NotNil(t, capturedOpts, "ImportFromFile should have been called")
60+
require.NotNil(t, capturedOpts.Path, "Path should be set")
61+
require.NotNil(t, capturedOpts.Name, "Name should be set so the display name matches -project")
62+
assert.Equal(t, "project-path", *capturedOpts.Path)
63+
assert.Equal(t, "project-path", *capturedOpts.Name)
64+
require.NotNil(t, capturedOpts.Namespace)
65+
assert.Equal(t, "namespace", *capturedOpts.Namespace)
5566
})
5667

5768
t.Run("ImportInitiationFails", func(t *testing.T) {

0 commit comments

Comments
 (0)