2017-04-21 14:32:31 +03:00
// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2017-05-07 17:40:31 +03:00
"container/list"
2019-06-30 10:57:59 +03:00
"crypto/sha1"
2017-04-21 14:32:31 +03:00
"fmt"
"strings"
2019-09-18 08:39:45 +03:00
"time"
2017-04-21 14:32:31 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2019-07-25 13:55:06 +03:00
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2017-04-21 14:32:31 +03:00
)
// CommitStatus holds a single Status of a single Commit
type CommitStatus struct {
2020-01-22 06:46:04 +03:00
ID int64 ` xorm:"pk autoincr" `
Index int64 ` xorm:"INDEX UNIQUE(repo_sha_index)" `
RepoID int64 ` xorm:"INDEX UNIQUE(repo_sha_index)" `
Repo * Repository ` xorm:"-" `
State api . CommitStatusState ` xorm:"VARCHAR(7) NOT NULL" `
SHA string ` xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)" `
TargetURL string ` xorm:"TEXT" `
Description string ` xorm:"TEXT" `
ContextHash string ` xorm:"char(40) index" `
Context string ` xorm:"TEXT" `
Creator * User ` xorm:"-" `
2017-04-21 14:32:31 +03:00
CreatorID int64
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2017-04-21 14:32:31 +03:00
}
func ( status * CommitStatus ) loadRepo ( e Engine ) ( err error ) {
if status . Repo == nil {
status . Repo , err = getRepositoryByID ( e , status . RepoID )
if err != nil {
return fmt . Errorf ( "getRepositoryByID [%d]: %v" , status . RepoID , err )
}
}
if status . Creator == nil && status . CreatorID > 0 {
status . Creator , err = getUserByID ( e , status . CreatorID )
if err != nil {
return fmt . Errorf ( "getUserByID [%d]: %v" , status . CreatorID , err )
}
}
return nil
}
// APIURL returns the absolute APIURL to this commit-status.
func ( status * CommitStatus ) APIURL ( ) string {
2019-06-12 22:41:28 +03:00
_ = status . loadRepo ( x )
2019-08-09 05:13:03 +03:00
return fmt . Sprintf ( "%sapi/v1/repos/%s/statuses/%s" ,
2017-04-21 14:32:31 +03:00
setting . AppURL , status . Repo . FullName ( ) , status . SHA )
}
// APIFormat assumes some fields assigned with values:
// Required - Repo, Creator
func ( status * CommitStatus ) APIFormat ( ) * api . Status {
2019-06-12 22:41:28 +03:00
_ = status . loadRepo ( x )
2017-04-21 14:32:31 +03:00
apiStatus := & api . Status {
2017-12-11 07:37:04 +03:00
Created : status . CreatedUnix . AsTime ( ) ,
Updated : status . CreatedUnix . AsTime ( ) ,
2017-04-21 14:32:31 +03:00
State : api . StatusState ( status . State ) ,
TargetURL : status . TargetURL ,
Description : status . Description ,
ID : status . Index ,
URL : status . APIURL ( ) ,
Context : status . Context ,
}
if status . Creator != nil {
apiStatus . Creator = status . Creator . APIFormat ( )
}
return apiStatus
}
2017-09-14 09:51:32 +03:00
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
func CalcCommitStatus ( statuses [ ] * CommitStatus ) * CommitStatus {
var lastStatus * CommitStatus
2020-01-22 06:46:04 +03:00
var state api . CommitStatusState
2017-09-14 09:51:32 +03:00
for _ , status := range statuses {
2020-01-22 06:46:04 +03:00
if status . State . NoBetterThan ( state ) {
2017-09-14 09:51:32 +03:00
state = status . State
lastStatus = status
}
}
if lastStatus == nil {
if len ( statuses ) > 0 {
lastStatus = statuses [ 0 ]
} else {
lastStatus = & CommitStatus { }
}
}
return lastStatus
}
2019-07-25 13:55:06 +03:00
// CommitStatusOptions holds the options for query commit statuses
type CommitStatusOptions struct {
Page int
State string
SortType string
}
2017-04-21 14:32:31 +03:00
// GetCommitStatuses returns all statuses for a given commit.
2019-07-25 13:55:06 +03:00
func GetCommitStatuses ( repo * Repository , sha string , opts * CommitStatusOptions ) ( [ ] * CommitStatus , int64 , error ) {
if opts . Page <= 0 {
opts . Page = 1
}
countSession := listCommitStatusesStatement ( repo , sha , opts )
maxResults , err := countSession . Count ( new ( CommitStatus ) )
if err != nil {
log . Error ( "Count PRs: %v" , err )
return nil , maxResults , err
}
statuses := make ( [ ] * CommitStatus , 0 , ItemsPerPage )
findSession := listCommitStatusesStatement ( repo , sha , opts )
sortCommitStatusesSession ( findSession , opts . SortType )
findSession . Limit ( ItemsPerPage , ( opts . Page - 1 ) * ItemsPerPage )
return statuses , maxResults , findSession . Find ( & statuses )
}
func listCommitStatusesStatement ( repo * Repository , sha string , opts * CommitStatusOptions ) * xorm . Session {
sess := x . Where ( "repo_id = ?" , repo . ID ) . And ( "sha = ?" , sha )
switch opts . State {
case "pending" , "success" , "error" , "failure" , "warning" :
sess . And ( "state = ?" , opts . State )
}
return sess
}
func sortCommitStatusesSession ( sess * xorm . Session , sortType string ) {
switch sortType {
case "oldest" :
sess . Asc ( "created_unix" )
case "recentupdate" :
sess . Desc ( "updated_unix" )
case "leastupdate" :
sess . Asc ( "updated_unix" )
case "leastindex" :
sess . Desc ( "index" )
case "highestindex" :
sess . Asc ( "index" )
default :
sess . Desc ( "created_unix" )
}
2017-04-21 14:32:31 +03:00
}
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
func GetLatestCommitStatus ( repo * Repository , sha string , page int ) ( [ ] * CommitStatus , error ) {
2017-05-07 17:40:31 +03:00
ids := make ( [ ] int64 , 0 , 10 )
err := x . Limit ( 10 , page * 10 ) .
Table ( & CommitStatus { } ) .
Where ( "repo_id = ?" , repo . ID ) . And ( "sha = ?" , sha ) .
Select ( "max( id ) as id" ) .
2019-06-30 10:57:59 +03:00
GroupBy ( "context_hash" ) . OrderBy ( "max( id ) desc" ) . Find ( & ids )
2017-05-07 17:40:31 +03:00
if err != nil {
return nil , err
}
statuses := make ( [ ] * CommitStatus , 0 , len ( ids ) )
if len ( ids ) == 0 {
return statuses , nil
}
return statuses , x . In ( "id" , ids ) . Find ( & statuses )
2017-04-21 14:32:31 +03:00
}
2019-09-18 08:39:45 +03:00
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
func FindRepoRecentCommitStatusContexts ( repoID int64 , before time . Duration ) ( [ ] string , error ) {
start := timeutil . TimeStampNow ( ) . AddDuration ( - before )
ids := make ( [ ] int64 , 0 , 10 )
if err := x . Table ( "commit_status" ) .
Where ( "repo_id = ?" , repoID ) .
And ( "updated_unix >= ?" , start ) .
Select ( "max( id ) as id" ) .
GroupBy ( "context_hash" ) . OrderBy ( "max( id ) desc" ) .
Find ( & ids ) ; err != nil {
return nil , err
}
var contexts = make ( [ ] string , 0 , len ( ids ) )
if len ( ids ) == 0 {
return contexts , nil
}
return contexts , x . Select ( "context" ) . Table ( "commit_status" ) . In ( "id" , ids ) . Find ( & contexts )
}
2017-04-21 14:32:31 +03:00
// NewCommitStatusOptions holds options for creating a CommitStatus
type NewCommitStatusOptions struct {
Repo * Repository
Creator * User
SHA string
CommitStatus * CommitStatus
}
2019-06-30 10:57:59 +03:00
// NewCommitStatus save commit statuses into database
func NewCommitStatus ( opts NewCommitStatusOptions ) error {
2017-04-21 14:32:31 +03:00
if opts . Repo == nil {
2019-06-30 10:57:59 +03:00
return fmt . Errorf ( "NewCommitStatus[nil, %s]: no repository specified" , opts . SHA )
2017-04-21 14:32:31 +03:00
}
2019-06-30 10:57:59 +03:00
repoPath := opts . Repo . RepoPath ( )
2017-04-21 14:32:31 +03:00
if opts . Creator == nil {
2019-06-30 10:57:59 +03:00
return fmt . Errorf ( "NewCommitStatus[%s, %s]: no user specified" , repoPath , opts . SHA )
2017-04-21 14:32:31 +03:00
}
2019-06-30 10:57:59 +03:00
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return fmt . Errorf ( "NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v" , opts . Repo . ID , opts . Creator . ID , opts . SHA , err )
2017-04-21 14:32:31 +03:00
}
2019-06-30 10:57:59 +03:00
opts . CommitStatus . Description = strings . TrimSpace ( opts . CommitStatus . Description )
opts . CommitStatus . Context = strings . TrimSpace ( opts . CommitStatus . Context )
opts . CommitStatus . TargetURL = strings . TrimSpace ( opts . CommitStatus . TargetURL )
opts . CommitStatus . SHA = opts . SHA
opts . CommitStatus . CreatorID = opts . Creator . ID
opts . CommitStatus . RepoID = opts . Repo . ID
2017-04-21 14:32:31 +03:00
// Get the next Status Index
var nextIndex int64
lastCommitStatus := & CommitStatus {
SHA : opts . SHA ,
RepoID : opts . Repo . ID ,
}
has , err := sess . Desc ( "index" ) . Limit ( 1 ) . Get ( lastCommitStatus )
if err != nil {
2019-06-12 22:41:28 +03:00
if err := sess . Rollback ( ) ; err != nil {
2019-06-30 10:57:59 +03:00
log . Error ( "NewCommitStatus: sess.Rollback: %v" , err )
2019-06-12 22:41:28 +03:00
}
2019-06-30 10:57:59 +03:00
return fmt . Errorf ( "NewCommitStatus[%s, %s]: %v" , repoPath , opts . SHA , err )
2017-04-21 14:32:31 +03:00
}
if has {
2019-06-30 10:57:59 +03:00
log . Debug ( "NewCommitStatus[%s, %s]: found" , repoPath , opts . SHA )
2017-04-21 14:32:31 +03:00
nextIndex = lastCommitStatus . Index
}
opts . CommitStatus . Index = nextIndex + 1
2019-06-30 10:57:59 +03:00
log . Debug ( "NewCommitStatus[%s, %s]: %d" , repoPath , opts . SHA , opts . CommitStatus . Index )
opts . CommitStatus . ContextHash = hashCommitStatusContext ( opts . CommitStatus . Context )
2017-04-21 14:32:31 +03:00
// Insert new CommitStatus
if _ , err = sess . Insert ( opts . CommitStatus ) ; err != nil {
2019-06-12 22:41:28 +03:00
if err := sess . Rollback ( ) ; err != nil {
2019-06-30 10:57:59 +03:00
log . Error ( "Insert CommitStatus: sess.Rollback: %v" , err )
2019-06-12 22:41:28 +03:00
}
2019-06-30 10:57:59 +03:00
return fmt . Errorf ( "Insert CommitStatus[%s, %s]: %v" , repoPath , opts . SHA , err )
2017-04-21 14:32:31 +03:00
}
2017-04-24 17:33:54 +03:00
return sess . Commit ( )
2017-04-21 14:32:31 +03:00
}
2017-05-07 17:40:31 +03:00
// SignCommitWithStatuses represents a commit with validation of signature and status state.
type SignCommitWithStatuses struct {
2017-09-14 09:51:32 +03:00
Status * CommitStatus
2017-05-07 17:40:31 +03:00
* SignCommit
}
// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state
func ParseCommitsWithStatus ( oldCommits * list . List , repo * Repository ) * list . List {
var (
newCommits = list . New ( )
e = oldCommits . Front ( )
)
for e != nil {
c := e . Value . ( SignCommit )
commit := SignCommitWithStatuses {
SignCommit : & c ,
}
2017-09-14 09:51:32 +03:00
statuses , err := GetLatestCommitStatus ( repo , commit . ID . String ( ) , 0 )
2017-05-07 17:40:31 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetLatestCommitStatus: %v" , err )
2017-05-07 17:40:31 +03:00
} else {
2017-09-14 09:51:32 +03:00
commit . Status = CalcCommitStatus ( statuses )
2017-05-07 17:40:31 +03:00
}
newCommits . PushBack ( commit )
e = e . Next ( )
}
return newCommits
}
2019-06-30 10:57:59 +03:00
// hashCommitStatusContext hash context
func hashCommitStatusContext ( context string ) string {
return fmt . Sprintf ( "%x" , sha1 . Sum ( [ ] byte ( context ) ) )
}