fix(glrepo): make Project() actually cache the fetched project

Description

glrepo.Project() has a value receiver, so the memo write inside it lands on a copy of the struct and is discarded:

func (r glRepo) Project(client *gitlab.Client) (*gitlab.Project, error) {   // value receiver
	if r.project != nil && ... { return r.project.Project, nil }
	p, err := api.GetProject(client, r.fullname)
	...
	r.project = &Project{...}   // written to the copy, thrown away

The cache has never worked. Every call re-fetches, despite the fullname/hostname cache-invalidation fields on the Project struct showing memoization was intended.

NewWithHost already returns *glRepo into the interface, and every caller is sequential, so a pointer receiver is safe and satisfies glrepo.Interface unchanged.

Knock-on fix

glab ci cancel job and glab ci cancel pipeline call Project() inside their per-item loop, so cancelling N items issued N identical GET /projects/:fullpath calls. Two test expectations encoded that repeat fetch verbatim:

gomock.InOrder(
    tc.MockProjects.EXPECT().GetProject("OWNER/REPO", ...),   // fetch
    tc.MockJobs.EXPECT().CancelJob(int64(123), int64(11111111)),
    tc.MockProjects.EXPECT().GetProject("OWNER/REPO", ...),   // same fetch again
    tc.MockJobs.EXPECT().CancelJob(int64(123), int64(22222222)),
)

They now expect a single lookup per batch.

Found while investigating #8526: the telemetry hook calls Project() too, so making telemetry delivery reliable would otherwise have added a redundant API round trip to every in-repo command.

No issue; found during #8526 investigation.

How has this been tested?

New Test_glRepo_Project_cachesTheFetch asserts a single fetch across two Project() calls. Verified it fails without the receiver change (2 calls, not 1) and passes with it.

make test-changed (3515 tests) and make lint pass. The two ci cancel expectations are the only fallout across the tree.

Edited by Kai Armstrong

Merge request reports

Loading
Loading