History

Move some code into models/git (#19879)

* Move access and repo permission to models/perm/access * fix test * Move some git related files into sub package models/git * Fix build * fix git test * move lfs to sub package * move more git related functions to models/git * Move functions sequence * Some improvements per @KN4CK3R and @delvh

Lunny Xiao committed 4 years ago

Commit 110fc571 parent a9dc9b067 files · +549 −495Browse files

Showing the changes to models/git/commit_status.go (1 of 67 files). Show all

Unified · Split

models/git/commit_status.go+370−0
@@ -0,0 +1,370 @@
1// Copyright 2017 Gitea. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package git
6
7import (
8 "context"
9 "crypto/sha1"
10 "fmt"
11 "net/url"
12 "strings"
13 "time"
14
15 asymkey_model "code.gitea.io/gitea/models/asymkey"
16 "code.gitea.io/gitea/models/db"
17 repo_model "code.gitea.io/gitea/models/repo"
18 user_model "code.gitea.io/gitea/models/user"
19 "code.gitea.io/gitea/modules/git"
20 "code.gitea.io/gitea/modules/log"
21 "code.gitea.io/gitea/modules/setting"
22 api "code.gitea.io/gitea/modules/structs"
23 "code.gitea.io/gitea/modules/timeutil"
24
25 "xorm.io/xorm"
26)
27
28// CommitStatus holds a single Status of a single Commit
29type CommitStatus struct {
30 ID int64 `xorm:"pk autoincr"`
31 Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
32 RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
33 Repo *repo_model.Repository `xorm:"-"`
34 State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
35 SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
36 TargetURL string `xorm:"TEXT"`
37 Description string `xorm:"TEXT"`
38 ContextHash string `xorm:"char(40) index"`
39 Context string `xorm:"TEXT"`
40 Creator *user_model.User `xorm:"-"`
41 CreatorID int64
42
43 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
44 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
45}
46
47func init() {
48 db.RegisterModel(new(CommitStatus))
49 db.RegisterModel(new(CommitStatusIndex))
50}
51
52// upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
53func upsertCommitStatusIndex(ctx context.Context, repoID int64, sha string) (err error) {
54 // An atomic UPSERT operation (INSERT/UPDATE) is the only operation
55 // that ensures that the key is actually locked.
56 switch {
57 case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
58 _, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
59 "VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1",
60 repoID, sha)
61 case setting.Database.UseMySQL:
62 _, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
63 "VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
64 repoID, sha)
65 case setting.Database.UseMSSQL:
66 // https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
67 _, err = db.Exec(ctx, "MERGE `commit_status_index` WITH (HOLDLOCK) as target "+
68 "USING (SELECT ? AS repo_id, ? AS sha) AS src "+
69 "ON src.repo_id = target.repo_id AND src.sha = target.sha "+
70 "WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
71 "WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) "+
72 "VALUES (src.repo_id, src.sha, 1);",
73 repoID, sha)
74 default:
75 return fmt.Errorf("database type not supported")
76 }
77 return
78}
79
80// GetNextCommitStatusIndex retried 3 times to generate a resource index
81func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
82 for i := 0; i < db.MaxDupIndexAttempts; i++ {
83 idx, err := getNextCommitStatusIndex(repoID, sha)
84 if err == db.ErrResouceOutdated {
85 continue
86 }
87 if err != nil {
88 return 0, err
89 }
90 return idx, nil
91 }
92 return 0, db.ErrGetResourceIndexFailed
93}
94
95// getNextCommitStatusIndex return the next index
96func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
97 ctx, commiter, err := db.TxContext()
98 if err != nil {
99 return 0, err
100 }
101 defer commiter.Close()
102
103 var preIdx int64
104 _, err = db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx)
105 if err != nil {
106 return 0, err
107 }
108
109 if err := upsertCommitStatusIndex(ctx, repoID, sha); err != nil {
110 return 0, err
111 }
112
113 var curIdx int64
114 has, err := db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?", repoID, sha, preIdx+1).Get(&curIdx)
115 if err != nil {
116 return 0, err
117 }
118 if !has {
119 return 0, db.ErrResouceOutdated
120 }
121 if err := commiter.Commit(); err != nil {
122 return 0, err
123 }
124 return curIdx, nil
125}
126
127func (status *CommitStatus) loadAttributes(ctx context.Context) (err error) {
128 if status.Repo == nil {
129 status.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, status.RepoID)
130 if err != nil {
131 return fmt.Errorf("getRepositoryByID [%d]: %v", status.RepoID, err)
132 }
133 }
134 if status.Creator == nil && status.CreatorID > 0 {
135 status.Creator, err = user_model.GetUserByIDCtx(ctx, status.CreatorID)
136 if err != nil {
137 return fmt.Errorf("getUserByID [%d]: %v", status.CreatorID, err)
138 }
139 }
140 return nil
141}
142
143// APIURL returns the absolute APIURL to this commit-status.
144func (status *CommitStatus) APIURL() string {
145 _ = status.loadAttributes(db.DefaultContext)
146 return status.Repo.APIURL() + "/statuses/" + url.PathEscape(status.SHA)
147}
148
149// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
150func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus {
151 var lastStatus *CommitStatus
152 var state api.CommitStatusState
153 for _, status := range statuses {
154 if status.State.NoBetterThan(state) {
155 state = status.State
156 lastStatus = status
157 }
158 }
159 if lastStatus == nil {
160 if len(statuses) > 0 {
161 lastStatus = statuses[0]
162 } else {
163 lastStatus = &CommitStatus{}
164 }
165 }
166 return lastStatus
167}
168
169// CommitStatusOptions holds the options for query commit statuses
170type CommitStatusOptions struct {
171 db.ListOptions
172 State string
173 SortType string
174}
175
176// GetCommitStatuses returns all statuses for a given commit.
177func GetCommitStatuses(repo *repo_model.Repository, sha string, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) {
178 if opts.Page <= 0 {
179 opts.Page = 1
180 }
181 if opts.PageSize <= 0 {
182 opts.Page = setting.ItemsPerPage
183 }
184
185 countSession := listCommitStatusesStatement(repo, sha, opts)
186 countSession = db.SetSessionPagination(countSession, opts)
187 maxResults, err := countSession.Count(new(CommitStatus))
188 if err != nil {
189 log.Error("Count PRs: %v", err)
190 return nil, maxResults, err
191 }
192
193 statuses := make([]*CommitStatus, 0, opts.PageSize)
194 findSession := listCommitStatusesStatement(repo, sha, opts)
195 findSession = db.SetSessionPagination(findSession, opts)
196 sortCommitStatusesSession(findSession, opts.SortType)
197 return statuses, maxResults, findSession.Find(&statuses)
198}
199
200func listCommitStatusesStatement(repo *repo_model.Repository, sha string, opts *CommitStatusOptions) *xorm.Session {
201 sess := db.GetEngine(db.DefaultContext).Where("repo_id = ?", repo.ID).And("sha = ?", sha)
202 switch opts.State {
203 case "pending", "success", "error", "failure", "warning":
204 sess.And("state = ?", opts.State)
205 }
206 return sess
207}
208
209func sortCommitStatusesSession(sess *xorm.Session, sortType string) {
210 switch sortType {
211 case "oldest":
212 sess.Asc("created_unix")
213 case "recentupdate":
214 sess.Desc("updated_unix")
215 case "leastupdate":
216 sess.Asc("updated_unix")
217 case "leastindex":
218 sess.Desc("index")
219 case "highestindex":
220 sess.Asc("index")
221 default:
222 sess.Desc("created_unix")
223 }
224}
225
226// CommitStatusIndex represents a table for commit status index
227type CommitStatusIndex struct {
228 ID int64
229 RepoID int64 `xorm:"unique(repo_sha)"`
230 SHA string `xorm:"unique(repo_sha)"`
231 MaxIndex int64 `xorm:"index"`
232}
233
234// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
235func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOptions db.ListOptions) ([]*CommitStatus, int64, error) {
236 ids := make([]int64, 0, 10)
237 sess := db.GetEngine(ctx).Table(&CommitStatus{}).
238 Where("repo_id = ?", repoID).And("sha = ?", sha).
239 Select("max( id ) as id").
240 GroupBy("context_hash").OrderBy("max( id ) desc")
241
242 sess = db.SetSessionPagination(sess, &listOptions)
243
244 count, err := sess.FindAndCount(&ids)
245 if err != nil {
246 return nil, count, err
247 }
248 statuses := make([]*CommitStatus, 0, len(ids))
249 if len(ids) == 0 {
250 return statuses, count, nil
251 }
252 return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses)
253}
254
255// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
256func FindRepoRecentCommitStatusContexts(repoID int64, before time.Duration) ([]string, error) {
257 start := timeutil.TimeStampNow().AddDuration(-before)
258 ids := make([]int64, 0, 10)
259 if err := db.GetEngine(db.DefaultContext).Table("commit_status").
260 Where("repo_id = ?", repoID).
261 And("updated_unix >= ?", start).
262 Select("max( id ) as id").
263 GroupBy("context_hash").OrderBy("max( id ) desc").
264 Find(&ids); err != nil {
265 return nil, err
266 }
267
268 contexts := make([]string, 0, len(ids))
269 if len(ids) == 0 {
270 return contexts, nil
271 }
272 return contexts, db.GetEngine(db.DefaultContext).Select("context").Table("commit_status").In("id", ids).Find(&contexts)
273}
274
275// NewCommitStatusOptions holds options for creating a CommitStatus
276type NewCommitStatusOptions struct {
277 Repo *repo_model.Repository
278 Creator *user_model.User
279 SHA string
280 CommitStatus *CommitStatus
281}
282
283// NewCommitStatus save commit statuses into database
284func NewCommitStatus(opts NewCommitStatusOptions) error {
285 if opts.Repo == nil {
286 return fmt.Errorf("NewCommitStatus[nil, %s]: no repository specified", opts.SHA)
287 }
288
289 repoPath := opts.Repo.RepoPath()
290 if opts.Creator == nil {
291 return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
292 }
293
294 // Get the next Status Index
295 idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
296 if err != nil {
297 return fmt.Errorf("generate commit status index failed: %v", err)
298 }
299
300 ctx, committer, err := db.TxContext()
301 if err != nil {
302 return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", opts.Repo.ID, opts.Creator.ID, opts.SHA, err)
303 }
304 defer committer.Close()
305
306 opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
307 opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
308 opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
309 opts.CommitStatus.SHA = opts.SHA
310 opts.CommitStatus.CreatorID = opts.Creator.ID
311 opts.CommitStatus.RepoID = opts.Repo.ID
312 opts.CommitStatus.Index = idx
313 log.Debug("NewCommitStatus[%s, %s]: %d", repoPath, opts.SHA, opts.CommitStatus.Index)
314
315 opts.CommitStatus.ContextHash = hashCommitStatusContext(opts.CommitStatus.Context)
316
317 // Insert new CommitStatus
318 if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil {
319 return fmt.Errorf("Insert CommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
320 }
321
322 return committer.Commit()
323}
324
325// SignCommitWithStatuses represents a commit with validation of signature and status state.
326type SignCommitWithStatuses struct {
327 Status *CommitStatus
328 Statuses []*CommitStatus
329 *asymkey_model.SignCommit
330}
331
332// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state
333func ParseCommitsWithStatus(oldCommits []*asymkey_model.SignCommit, repo *repo_model.Repository) []*SignCommitWithStatuses {
334 newCommits := make([]*SignCommitWithStatuses, 0, len(oldCommits))
335
336 for _, c := range oldCommits {
337 commit := &SignCommitWithStatuses{
338 SignCommit: c,
339 }
340 statuses, _, err := GetLatestCommitStatus(db.DefaultContext, repo.ID, commit.ID.String(), db.ListOptions{})
341 if err != nil {
342 log.Error("GetLatestCommitStatus: %v", err)
343 } else {
344 commit.Statuses = statuses
345 commit.Status = CalcCommitStatus(statuses)
346 }
347
348 newCommits = append(newCommits, commit)
349 }
350 return newCommits
351}
352
353// hashCommitStatusContext hash context
354func hashCommitStatusContext(context string) string {
355 return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
356}
357
358// ConvertFromGitCommit converts git commits into SignCommitWithStatuses
359func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses {
360 return ParseCommitsWithStatus(
361 asymkey_model.ParseCommitsWithSignature(
362 user_model.ValidateCommitsWithEmails(commits),
363 repo.GetTrustModel(),
364 func(user *user_model.User) (bool, error) {
365 return repo_model.IsOwnerMemberCollaborator(repo, user.ID)
366 },
367 ),
368 repo,
369 )
370}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t