-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathservice.go
More file actions
195 lines (165 loc) · 5.27 KB
/
service.go
File metadata and controls
195 lines (165 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package cpgo
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
)
var ErrUnmanagedPullRequest = errors.New("existing pull request is not managed by cpgo")
// Dependencies bundles runtime ports required by Service.
type Dependencies struct {
ProfileFetcher ProfileFetcher
ProfileValidator ProfileValidator
BranchWriter BranchWriter
PullRequests PullRequestService
}
// Service orchestrates one cpgo execution using injected ports.
type Service struct {
profileFetcher ProfileFetcher
profileValidator ProfileValidator
branchWriter BranchWriter
pullRequests PullRequestService
}
// RunResult summarizes what changed during one run.
type RunResult struct {
BaseBranch string
HeadBranch string
PullRequestNumber int
CommitSHA string
IsProfileChanged bool
IsPullRequestCreated bool
IsNoop bool
}
// NewService validates dependencies and returns an executable service.
func NewService(deps Dependencies) (*Service, error) {
switch {
case deps.ProfileFetcher == nil:
return nil, fmt.Errorf("profile fetcher is required")
case deps.ProfileValidator == nil:
return nil, fmt.Errorf("profile validator is required")
case deps.BranchWriter == nil:
return nil, fmt.Errorf("branch writer is required")
case deps.PullRequests == nil:
return nil, fmt.Errorf("pull request service is required")
}
return &Service{
profileFetcher: deps.ProfileFetcher,
profileValidator: deps.ProfileValidator,
branchWriter: deps.BranchWriter,
pullRequests: deps.PullRequests,
}, nil
}
// Run executes a full fetch-validate-write-pr cycle for one request.
func (svc *Service) Run(ctx context.Context, req RunRequest) (RunResult, error) {
normalized, err := req.normalized()
if err != nil {
return RunResult{}, err
}
repository := RepositoryRef{
Owner: normalized.Repository.Owner,
Name: normalized.Repository.Name,
}
profile, err := svc.profileFetcher.FetchCPUProfile(ctx, FetchProfileRequest{
URL: normalized.Profile.URL,
Seconds: normalized.Profile.Seconds,
Headers: normalized.Profile.Headers,
})
if err != nil {
return RunResult{}, fmt.Errorf("fetch cpu profile: %w", err)
}
if err := svc.profileValidator.ValidateCPUProfile(profile); err != nil {
return RunResult{}, fmt.Errorf("validate cpu profile: %w", err)
}
baseBranch, err := svc.resolveBaseBranch(ctx, repository, normalized.Repository.BaseBranch)
if err != nil {
return RunResult{}, err
}
openPR, err := svc.pullRequests.FindOpenByHead(ctx, FindPullRequestRequest{
Repository: repository,
BaseBranch: baseBranch,
HeadBranch: normalized.Repository.HeadBranch,
})
if err != nil {
return RunResult{}, fmt.Errorf("find open pull request: %w", err)
}
if openPR != nil && !strings.Contains(openPR.Body, normalized.PullRequest.ManagedByMarker) {
return RunResult{}, ErrUnmanagedPullRequest
}
readResult, err := svc.branchWriter.ReadFile(ctx, ReadFileRequest{
Repository: repository,
Branch: baseBranch,
Path: normalized.Repository.PGOPath,
})
if err != nil {
return RunResult{}, fmt.Errorf("read base branch pgo file: %w", err)
}
if readResult.HasFile && bytes.Equal(readResult.Content, profile) {
return RunResult{
BaseBranch: baseBranch,
HeadBranch: normalized.Repository.HeadBranch,
PullRequestNumber: prNumber(openPR),
IsNoop: true,
}, nil
}
writeResult, err := svc.branchWriter.UpsertFileAndForceBranch(ctx, UpsertFileRequest{
Repository: repository,
BaseBranch: baseBranch,
HeadBranch: normalized.Repository.HeadBranch,
Path: normalized.Repository.PGOPath,
Content: profile,
CommitMessage: normalized.Commit.Message,
})
if err != nil {
return RunResult{}, fmt.Errorf("update pgo branch: %w", err)
}
result := RunResult{
BaseBranch: baseBranch,
HeadBranch: normalized.Repository.HeadBranch,
CommitSHA: writeResult.CommitSHA,
IsProfileChanged: true,
}
if openPR != nil {
result.PullRequestNumber = openPR.Number
return result, nil
}
createdPR, err := svc.pullRequests.Create(ctx, CreatePullRequestRequest{
Repository: repository,
BaseBranch: baseBranch,
HeadBranch: normalized.Repository.HeadBranch,
Title: normalized.PullRequest.Title,
Body: appendMarker(normalized.PullRequest.Body, normalized.PullRequest.ManagedByMarker),
})
if err != nil {
return RunResult{}, fmt.Errorf("create pull request: %w", err)
}
result.PullRequestNumber = createdPR.Number
result.IsPullRequestCreated = true
return result, nil
}
// resolveBaseBranch picks the configured base or repository default branch.
func (svc *Service) resolveBaseBranch(ctx context.Context, repository RepositoryRef, baseBranchCfg string) (string, error) {
if strings.TrimSpace(baseBranchCfg) != "" {
return baseBranchCfg, nil
}
baseBranch, err := svc.branchWriter.DefaultBranch(ctx, repository)
if err != nil {
return "", fmt.Errorf("resolve default branch: %w", err)
}
return baseBranch, nil
}
func prNumber(existing *PullRequest) int {
if existing == nil {
return 0
}
return existing.Number
}
func appendMarker(body string, marker string) string {
if strings.Contains(body, marker) {
return body
}
if strings.TrimSpace(body) == "" {
return marker
}
return strings.TrimRight(body, "\n") + "\n\n" + marker
}