diff --git a/pkg/cluster/controller/projects/projects/zz_project.go b/pkg/cluster/controller/projects/projects/zz_project.go index 3107ce20..13b5ce24 100644 --- a/pkg/cluster/controller/projects/projects/zz_project.go +++ b/pkg/cluster/controller/projects/projects/zz_project.go @@ -371,7 +371,11 @@ func (e *external) lateInitialize(ctx context.Context, cr *v1alpha1.Project, pro in.ContainerRegistryAccessLevel = clients.LateInitializeAccessControlValue(in.ContainerRegistryAccessLevel, project.ContainerRegistryAccessLevel) } - in.DefaultBranch = clients.LateInitializeStringPtr(in.DefaultBranch, project.DefaultBranch) + // GitLab reports a default branch for empty repositories although the + // branch does not exist yet, and rejects it on edit. + if !project.EmptyRepo { + in.DefaultBranch = clients.LateInitializeStringPtr(in.DefaultBranch, project.DefaultBranch) + } in.Description = clients.LateInitializeStringPtr(in.Description, project.Description) in.EnvironmentsAccessLevel = clients.LateInitializeAccessControlValue(in.EnvironmentsAccessLevel, project.EnvironmentsAccessLevel) in.FeatureFlagsAccessLevel = clients.LateInitializeAccessControlValue(in.FeatureFlagsAccessLevel, project.FeatureFlagsAccessLevel) diff --git a/pkg/cluster/controller/projects/projects/zz_project_test.go b/pkg/cluster/controller/projects/projects/zz_project_test.go index c9ea292d..2358ed58 100644 --- a/pkg/cluster/controller/projects/projects/zz_project_test.go +++ b/pkg/cluster/controller/projects/projects/zz_project_test.go @@ -2088,3 +2088,51 @@ func TestArchiveConverges(t *testing.T) { }) } } + +// TestLateInitializeDefaultBranch guards against late-initializing the default +// branch GitLab reports for an empty repository, which does not exist yet and +// is rejected on edit. An explicitly set default branch is kept so GitLab's +// error still surfaces. +func TestLateInitializeDefaultBranch(t *testing.T) { + cases := map[string]struct { + spec *string + project *gitlab.Project + want *string + }{ + "EmptyRepoUnset": { + project: &gitlab.Project{DefaultBranch: "main", EmptyRepo: true}, + want: nil, + }, + "EmptyRepoExplicit": { + spec: ptr.To("develop"), + project: &gitlab.Project{DefaultBranch: "main", EmptyRepo: true}, + want: ptr.To("develop"), + }, + "NonEmptyRepoUnset": { + project: &gitlab.Project{DefaultBranch: "main"}, + want: ptr.To("main"), + }, + "NonEmptyRepoExplicit": { + spec: ptr.To("develop"), + project: &gitlab.Project{DefaultBranch: "main"}, + want: ptr.To("develop"), + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &external{client: &fake.MockClient{ + MockGetProjectPushRules: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectPushRules, *gitlab.Response, error) { + return nil, &gitlab.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, nil + }, + }} + cr := &v1alpha1.Project{Spec: v1alpha1.ProjectSpec{ForProvider: v1alpha1.ProjectParameters{DefaultBranch: tc.spec}}} + + if err := e.lateInitialize(context.Background(), cr, tc.project); err != nil { + t.Fatalf("lateInitialize returned error: %v", err) + } + if diff := cmp.Diff(tc.want, cr.Spec.ForProvider.DefaultBranch); diff != "" { + t.Errorf("DefaultBranch: -want, +got:\n%s", diff) + } + }) + } +} diff --git a/pkg/namespaced/controller/projects/projects/project.go b/pkg/namespaced/controller/projects/projects/project.go index 5b618031..74b89eea 100644 --- a/pkg/namespaced/controller/projects/projects/project.go +++ b/pkg/namespaced/controller/projects/projects/project.go @@ -369,7 +369,11 @@ func (e *external) lateInitialize(ctx context.Context, cr *v1alpha1.Project, pro in.ContainerRegistryAccessLevel = clients.LateInitializeAccessControlValue(in.ContainerRegistryAccessLevel, project.ContainerRegistryAccessLevel) } - in.DefaultBranch = clients.LateInitializeStringPtr(in.DefaultBranch, project.DefaultBranch) + // GitLab reports a default branch for empty repositories although the + // branch does not exist yet, and rejects it on edit. + if !project.EmptyRepo { + in.DefaultBranch = clients.LateInitializeStringPtr(in.DefaultBranch, project.DefaultBranch) + } in.Description = clients.LateInitializeStringPtr(in.Description, project.Description) in.EnvironmentsAccessLevel = clients.LateInitializeAccessControlValue(in.EnvironmentsAccessLevel, project.EnvironmentsAccessLevel) in.FeatureFlagsAccessLevel = clients.LateInitializeAccessControlValue(in.FeatureFlagsAccessLevel, project.FeatureFlagsAccessLevel) diff --git a/pkg/namespaced/controller/projects/projects/project_test.go b/pkg/namespaced/controller/projects/projects/project_test.go index 1e59b413..916b9c2d 100644 --- a/pkg/namespaced/controller/projects/projects/project_test.go +++ b/pkg/namespaced/controller/projects/projects/project_test.go @@ -2086,3 +2086,51 @@ func TestArchiveConverges(t *testing.T) { }) } } + +// TestLateInitializeDefaultBranch guards against late-initializing the default +// branch GitLab reports for an empty repository, which does not exist yet and +// is rejected on edit. An explicitly set default branch is kept so GitLab's +// error still surfaces. +func TestLateInitializeDefaultBranch(t *testing.T) { + cases := map[string]struct { + spec *string + project *gitlab.Project + want *string + }{ + "EmptyRepoUnset": { + project: &gitlab.Project{DefaultBranch: "main", EmptyRepo: true}, + want: nil, + }, + "EmptyRepoExplicit": { + spec: ptr.To("develop"), + project: &gitlab.Project{DefaultBranch: "main", EmptyRepo: true}, + want: ptr.To("develop"), + }, + "NonEmptyRepoUnset": { + project: &gitlab.Project{DefaultBranch: "main"}, + want: ptr.To("main"), + }, + "NonEmptyRepoExplicit": { + spec: ptr.To("develop"), + project: &gitlab.Project{DefaultBranch: "main"}, + want: ptr.To("develop"), + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &external{client: &fake.MockClient{ + MockGetProjectPushRules: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectPushRules, *gitlab.Response, error) { + return nil, &gitlab.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, nil + }, + }} + cr := &v1alpha1.Project{Spec: v1alpha1.ProjectSpec{ForProvider: v1alpha1.ProjectParameters{DefaultBranch: tc.spec}}} + + if err := e.lateInitialize(context.Background(), cr, tc.project); err != nil { + t.Fatalf("lateInitialize returned error: %v", err) + } + if diff := cmp.Diff(tc.want, cr.Spec.ForProvider.DefaultBranch); diff != "" { + t.Errorf("DefaultBranch: -want, +got:\n%s", diff) + } + }) + } +}