diff --git a/common/gitea_utils.go b/common/gitea_utils.go index c3358c9..f55aab4 100644 --- a/common/gitea_utils.go +++ b/common/gitea_utils.go @@ -29,6 +29,7 @@ import ( "path" "path/filepath" "slices" + "sync" "time" transport "github.com/go-openapi/runtime/client" @@ -66,6 +67,14 @@ const ( ReviewStateUnknown models.ReviewStateType = "" ) +type GiteaLabelGetter interface { + GetLabels(org, repo string, idx int64) ([]*models.Label, error) +} + +type GiteaLabelSettter interface { + SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) +} + type GiteaTimelineFetcher interface { GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) } @@ -182,6 +191,8 @@ type Gitea interface { GiteaCommitStatusGetter GiteaCommitStatusSetter GiteaSetRepoOptions + GiteaLabelGetter + GiteaLabelSettter GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error) @@ -189,7 +200,7 @@ type Gitea interface { GetOrganization(orgName string) (*models.Organization, error) GetOrganizationRepositories(orgName string) ([]*models.Repository, error) CreateRepositoryIfNotExist(git Git, org, repoName string) (*models.Repository, error) - CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) + CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, string, error) GetRecentPullRequests(org, repo, branch string) ([]*models.PullRequest, error) GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error) @@ -466,6 +477,30 @@ func (gitea *GiteaTransport) SetRepoOptions(owner, repo string, manual_merge boo return ok.Payload, err } +func (gitea *GiteaTransport) GetLabels(owner, repo string, idx int64) ([]*models.Label, error) { + ret, err := gitea.client.Issue.IssueGetLabels(issue.NewIssueGetLabelsParams().WithOwner(owner).WithRepo(repo).WithIndex(idx), gitea.transport.DefaultAuthentication) + if err != nil { + return nil, err + } + return ret.Payload, err +} + +func (gitea *GiteaTransport) SetLabels(owner, repo string, idx int64, labels []string) ([]*models.Label, error) { + interfaceLabels := make([]interface{}, len(labels)) + for i, l := range labels { + interfaceLabels[i] = l + } + + ret, err := gitea.client.Issue.IssueAddLabel(issue.NewIssueAddLabelParams().WithOwner(owner).WithRepo(repo).WithIndex(idx).WithBody(&models.IssueLabelsOption{Labels: interfaceLabels}), + gitea.transport.DefaultAuthentication) + + if err != nil { + return nil, err + } + + return ret.Payload, nil +} + const ( GiteaNotificationType_Pull = "Pull" ) @@ -643,7 +678,7 @@ func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org, repoName s return repo.Payload, nil } -func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) { +func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) { prOptions := models.CreatePullRequestOption{ Base: targetId, Head: srcId, @@ -659,7 +694,7 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository WithHead(srcId), gitea.transport.DefaultAuthentication, ); err == nil && pr.Payload.State == "open" { - return pr.Payload, nil + return pr.Payload, nil, false } pr, err := gitea.client.Repository.RepoCreatePullRequest( @@ -673,10 +708,10 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository ) if err != nil { - return nil, fmt.Errorf("Cannot create pull request. %w", err) + return nil, fmt.Errorf("Cannot create pull request. %w", err), true } - return pr.GetPayload(), nil + return pr.GetPayload(), nil, true } func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) { @@ -763,45 +798,78 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string) return nil } +type TimelineCacheData struct { + data []*models.TimelineComment + lastCheck time.Time +} + +var giteaTimelineCache map[string]TimelineCacheData +var giteaTimelineCacheMutex sync.RWMutex + func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) { page := int64(1) resCount := 1 - retData := []*models.TimelineComment{} + prID := fmt.Sprintf("%s/%s!%d", org, repo, idx) + giteaTimelineCacheMutex.RLock() + TimelineCache, IsCached := giteaTimelineCache[prID] + var LastCachedTime strfmt.DateTime + if IsCached { + l := len(TimelineCache.data) + if l > 0 { + LastCachedTime = TimelineCache.data[0].Updated + } + + // cache data for 5 seconds + if TimelineCache.lastCheck.Add(time.Second*5).Compare(time.Now()) > 0 { + giteaTimelineCacheMutex.RUnlock() + return TimelineCache.data, nil + } + } + giteaTimelineCacheMutex.RUnlock() + + giteaTimelineCacheMutex.Lock() + defer giteaTimelineCacheMutex.Unlock() for resCount > 0 { - res, err := gitea.client.Issue.IssueGetCommentsAndTimeline( - issue.NewIssueGetCommentsAndTimelineParams(). - WithOwner(org). - WithRepo(repo). - WithIndex(idx). - WithPage(&page), - gitea.transport.DefaultAuthentication, - ) - + opts := issue.NewIssueGetCommentsAndTimelineParams().WithOwner(org).WithRepo(repo).WithIndex(idx).WithPage(&page) + if !LastCachedTime.IsZero() { + opts = opts.WithSince(&LastCachedTime) + } + res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(opts, gitea.transport.DefaultAuthentication) if err != nil { return nil, err } - resCount = len(res.Payload) - LogDebug("page:", page, "len:", resCount) - if resCount == 0 { + if resCount = len(res.Payload); resCount == 0 { break } - page++ for _, d := range res.Payload { if d != nil { - retData = append(retData, d) + if time.Time(d.Created).Compare(time.Time(LastCachedTime)) > 0 { + // created after last check, so we append here + TimelineCache.data = append(TimelineCache.data, d) + } else { + // we need something updated in the timeline, maybe + } } } + + if resCount < 10 { + break + } + page++ } - LogDebug("total results:", len(retData)) - slices.SortFunc(retData, func(a, b *models.TimelineComment) int { + LogDebug("timeline", prID, "# timeline:", len(TimelineCache.data)) + slices.SortFunc(TimelineCache.data, func(a, b *models.TimelineComment) int { return time.Time(b.Created).Compare(time.Time(a.Created)) }) - return retData, nil + TimelineCache.lastCheck = time.Now() + giteaTimelineCache[prID] = TimelineCache + + return TimelineCache.data, nil } func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, string, error) { diff --git a/common/mock/gitea_utils.go b/common/mock/gitea_utils.go index 73e5387..f4fa8c9 100644 --- a/common/mock/gitea_utils.go +++ b/common/mock/gitea_utils.go @@ -18,6 +18,132 @@ import ( models "src.opensuse.org/autogits/common/gitea-generated/models" ) +// MockGiteaLabelGetter is a mock of GiteaLabelGetter interface. +type MockGiteaLabelGetter struct { + ctrl *gomock.Controller + recorder *MockGiteaLabelGetterMockRecorder + isgomock struct{} +} + +// MockGiteaLabelGetterMockRecorder is the mock recorder for MockGiteaLabelGetter. +type MockGiteaLabelGetterMockRecorder struct { + mock *MockGiteaLabelGetter +} + +// NewMockGiteaLabelGetter creates a new mock instance. +func NewMockGiteaLabelGetter(ctrl *gomock.Controller) *MockGiteaLabelGetter { + mock := &MockGiteaLabelGetter{ctrl: ctrl} + mock.recorder = &MockGiteaLabelGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGiteaLabelGetter) EXPECT() *MockGiteaLabelGetterMockRecorder { + return m.recorder +} + +// GetLabels mocks base method. +func (m *MockGiteaLabelGetter) GetLabels(org, repo string, idx int64) ([]*models.Label, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLabels", org, repo, idx) + ret0, _ := ret[0].([]*models.Label) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLabels indicates an expected call of GetLabels. +func (mr *MockGiteaLabelGetterMockRecorder) GetLabels(org, repo, idx any) *MockGiteaLabelGetterGetLabelsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGiteaLabelGetter)(nil).GetLabels), org, repo, idx) + return &MockGiteaLabelGetterGetLabelsCall{Call: call} +} + +// MockGiteaLabelGetterGetLabelsCall wrap *gomock.Call +type MockGiteaLabelGetterGetLabelsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGiteaLabelGetterGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelGetterGetLabelsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGiteaLabelGetterGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGiteaLabelGetterGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockGiteaLabelSettter is a mock of GiteaLabelSettter interface. +type MockGiteaLabelSettter struct { + ctrl *gomock.Controller + recorder *MockGiteaLabelSettterMockRecorder + isgomock struct{} +} + +// MockGiteaLabelSettterMockRecorder is the mock recorder for MockGiteaLabelSettter. +type MockGiteaLabelSettterMockRecorder struct { + mock *MockGiteaLabelSettter +} + +// NewMockGiteaLabelSettter creates a new mock instance. +func NewMockGiteaLabelSettter(ctrl *gomock.Controller) *MockGiteaLabelSettter { + mock := &MockGiteaLabelSettter{ctrl: ctrl} + mock.recorder = &MockGiteaLabelSettterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGiteaLabelSettter) EXPECT() *MockGiteaLabelSettterMockRecorder { + return m.recorder +} + +// SetLabels mocks base method. +func (m *MockGiteaLabelSettter) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels) + ret0, _ := ret[0].([]*models.Label) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLabels indicates an expected call of SetLabels. +func (mr *MockGiteaLabelSettterMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaLabelSettterSetLabelsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGiteaLabelSettter)(nil).SetLabels), org, repo, idx, labels) + return &MockGiteaLabelSettterSetLabelsCall{Call: call} +} + +// MockGiteaLabelSettterSetLabelsCall wrap *gomock.Call +type MockGiteaLabelSettterSetLabelsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGiteaLabelSettterSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelSettterSetLabelsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGiteaLabelSettterSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGiteaLabelSettterSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface. type MockGiteaTimelineFetcher struct { ctrl *gomock.Controller @@ -1850,12 +1976,13 @@ func (c *MockGiteaAddReviewCommentCall) DoAndReturn(f func(*models.PullRequest, } // CreatePullRequestIfNotExist mocks base method. -func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) { +func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreatePullRequestIfNotExist", repo, srcId, targetId, title, body) ret0, _ := ret[0].(*models.PullRequest) ret1, _ := ret[1].(error) - return ret0, ret1 + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 } // CreatePullRequestIfNotExist indicates an expected call of CreatePullRequestIfNotExist. @@ -1871,19 +1998,19 @@ type MockGiteaCreatePullRequestIfNotExistCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaCreatePullRequestIfNotExistCall { - c.Call = c.Call.Return(arg0, arg1) +func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error, arg2 bool) *MockGiteaCreatePullRequestIfNotExistCall { + c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall { +func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall { +func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -2202,6 +2329,45 @@ func (c *MockGiteaGetIssueCommentsCall) DoAndReturn(f func(string, string, int64 return c } +// GetLabels mocks base method. +func (m *MockGitea) GetLabels(org, repo string, idx int64) ([]*models.Label, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLabels", org, repo, idx) + ret0, _ := ret[0].([]*models.Label) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLabels indicates an expected call of GetLabels. +func (mr *MockGiteaMockRecorder) GetLabels(org, repo, idx any) *MockGiteaGetLabelsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGitea)(nil).GetLabels), org, repo, idx) + return &MockGiteaGetLabelsCall{Call: call} +} + +// MockGiteaGetLabelsCall wrap *gomock.Call +type MockGiteaGetLabelsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGiteaGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaGetLabelsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGiteaGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGiteaGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetNotifications mocks base method. func (m *MockGitea) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) { m.ctrl.T.Helper() @@ -2793,6 +2959,45 @@ func (c *MockGiteaSetCommitStatusCall) DoAndReturn(f func(string, string, string return c } +// SetLabels mocks base method. +func (m *MockGitea) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels) + ret0, _ := ret[0].([]*models.Label) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetLabels indicates an expected call of SetLabels. +func (mr *MockGiteaMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaSetLabelsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGitea)(nil).SetLabels), org, repo, idx, labels) + return &MockGiteaSetLabelsCall{Call: call} +} + +// MockGiteaSetLabelsCall wrap *gomock.Call +type MockGiteaSetLabelsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockGiteaSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaSetLabelsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockGiteaSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockGiteaSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // SetNotificationRead mocks base method. func (m *MockGitea) SetNotificationRead(notificationId int64) error { m.ctrl.T.Helper() diff --git a/workflow-pr/pr_processor.go b/workflow-pr/pr_processor.go index ce8f33f..84d9c61 100644 --- a/workflow-pr/pr_processor.go +++ b/workflow-pr/pr_processor.go @@ -227,12 +227,18 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet } title, desc := PrjGitDescription(prset) - pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc) + pr, err, isNew := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc) if err != nil { common.LogError("Error creating PrjGit PR:", err) return err } - Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, pr.Index, &models.EditPullRequestOption{ + org := PrjGit.Owner.UserName + repo := PrjGit.Name + idx := pr.Index + if isNew { + Gitea.SetLabels(org, repo, idx, []string{"staging/Auto"}) + } + Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{ RemoveDeadline: true, }) diff --git a/workflow-pr/pr_processor_opened_test.go b/workflow-pr/pr_processor_opened_test.go index e563586..0864711 100644 --- a/workflow-pr/pr_processor_opened_test.go +++ b/workflow-pr/pr_processor_opened_test.go @@ -103,7 +103,7 @@ func TestOpenPR(t *testing.T) { } // gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil) gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil) - gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil) + gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true) gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil) gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, nil) gitea.EXPECT().GetPullRequestReviews("test", "testRepo", int64(0)).Return([]*models.PullReview{}, nil) @@ -153,7 +153,7 @@ func TestOpenPR(t *testing.T) { } failedErr := errors.New("Returned error here") gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil) - gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr) + gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr, false) err := pr.Process(event) if err != failedErr { @@ -193,7 +193,7 @@ func TestOpenPR(t *testing.T) { gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil) gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil) gitea.EXPECT().GetPullRequestReviews("org", "SomeRepo", int64(13)).Return([]*models.PullReview{}, nil) - gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil) + gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true) gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr) gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())