Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Status-API #1332

Merged
merged 1 commit into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions models/fixtures/commit_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
-
id: 1
index: 1
repo_id: 1
state: "pending"
sha: "1234123412341234123412341234123412341234"
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
creator_id: 2

-
id: 2
index: 2
repo_id: 1
state: "warning"
sha: "1234123412341234123412341234123412341234"
target_url: https://example.com/converage/
description: My awesome Coverage service
context: cov/awesomeness
creator_id: 2

-
id: 3
index: 3
repo_id: 1
state: "success"
sha: "1234123412341234123412341234123412341234"
target_url: https://example.com/converage/
description: My awesome Coverage service
context: cov/awesomeness
creator_id: 2

-
id: 4
index: 4
repo_id: 1
state: "failure"
sha: "1234123412341234123412341234123412341234"
target_url: https://example.com/builds/
description: My awesome CI-service
context: ci/awesomeness
creator_id: 2

-
id: 5
index: 5
repo_id: 1
state: "error"
sha: "1234123412341234123412341234123412341234"
target_url: https://example.com/builds/
description: My awesome deploy service
context: deploy/awesomeness
creator_id: 2
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ var migrations = []Migration{
NewMigration("change mirror interval from hours to time.Duration", convertIntervalToDuration),
// v28 -> v29
NewMigration("add field for repo size", addRepoSize),
// v29 -> v30
NewMigration("add commit status table", addCommitStatus),
}

// Migrate database to current version
Expand Down
34 changes: 34 additions & 0 deletions models/migrations/v29.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2017 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"fmt"

"github.com/go-xorm/xorm"
)

// CommitStatus see models/status.go
type CommitStatus struct {
ID int64 `xorm:"pk autoincr"`
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
State string `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"`
Context string `xorm:"TEXT"`
CreatorID int64 `xorm:"INDEX"`

CreatedUnix int64 `xorm:"INDEX"`
UpdatedUnix int64 `xorm:"INDEX"`
}

func addCommitStatus(x *xorm.Engine) error {
if err := x.Sync2(new(CommitStatus)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func init() {
new(ProtectedBranch),
new(UserOpenID),
new(IssueWatch),
new(CommitStatus),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
265 changes: 265 additions & 0 deletions models/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// 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 (
"fmt"
"strings"
"time"

"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea"

"github.com/go-xorm/xorm"
)

// CommitStatusState holds the state of a Status
// It can be "pending", "success", "error", "failure", and "warning"
type CommitStatusState string

// IsWorseThan returns true if this State is worse than the given State
func (css CommitStatusState) IsWorseThan(css2 CommitStatusState) bool {
switch css {
case CommitStatusError:
return true
case CommitStatusFailure:
return css2 != CommitStatusError
case CommitStatusWarning:
return css2 != CommitStatusError && css2 != CommitStatusFailure
case CommitStatusSuccess:
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
default:
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusSuccess
}
}

const (
// CommitStatusPending is for when the Status is Pending
CommitStatusPending CommitStatusState = "pending"
// CommitStatusSuccess is for when the Status is Success
CommitStatusSuccess CommitStatusState = "success"
// CommitStatusError is for when the Status is Error
CommitStatusError CommitStatusState = "error"
// CommitStatusFailure is for when the Status is Failure
CommitStatusFailure CommitStatusState = "failure"
// CommitStatusWarning is for when the Status is Warning
CommitStatusWarning CommitStatusState = "warning"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I never saw that in any other hosting software. From my point of view, this state is not necessary. Warnings are part of the build processes, never the less its still successful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an idea that I got then I have flaky tests/internet. Basically if a test fails I wanna restart it once automatically, if it success on second try mark with 'warning' 🙂 (good for when you're getting rate-limited by Alpines Repo CDN 😂 )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, it's a super-set of GitHub API, so it won't hurt to have it 🙂

Copy link

@go2sh go2sh Mar 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, wasn't sure about the use case. ;-) I never got in touch which such problems since all our company builds run locally without any external dependencies. On the other hand, having such problems in your tests is also not ideal.^^

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, got the initial idea when I had intermittant race-conditions in my tests. Re-running the tests made 'em pass, but you should obviously fix the race-condition, hence "warning" 😉

The reason I want it thought is mainly for when you're rate-limited. I want it to return "warning" since you should fix the issue, but it might not be critical to fix it now 🙂

)

// CommitStatus holds a single Status of a single Commit
type CommitStatus struct {
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 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"`
Context string `xorm:"TEXT"`
Creator *User `xorm:"-"`
CreatorID int64

Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"INDEX"`
}

// BeforeInsert is invoked from XORM before inserting an object of this type.
func (status *CommitStatus) BeforeInsert() {
status.CreatedUnix = time.Now().Unix()
status.UpdatedUnix = status.CreatedUnix
}

// BeforeUpdate is invoked from XORM before updating this object.
func (status *CommitStatus) BeforeUpdate() {
status.UpdatedUnix = time.Now().Unix()
}

// AfterSet is invoked from XORM after setting the value of a field of
// this object.
func (status *CommitStatus) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
status.Created = time.Unix(status.CreatedUnix, 0).Local()
case "updated_unix":
status.Updated = time.Unix(status.UpdatedUnix, 0).Local()
}
}

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 {
status.loadRepo(x)
return fmt.Sprintf("%sapi/v1/%s/statuses/%s",
setting.AppURL, status.Repo.FullName(), status.SHA)
}

// APIFormat assumes some fields assigned with values:
// Required - Repo, Creator
func (status *CommitStatus) APIFormat() *api.Status {
status.loadRepo(x)
apiStatus := &api.Status{
Created: status.Created,
Updated: status.Created,
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
}

// GetCommitStatuses returns all statuses for a given commit.
func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
statuses := make([]*CommitStatus, 0, 10)
sess := x.NewSession()
defer sess.Close()
return statuses, sess.Limit(10, page*10).Where("repo_id = ?", repo.ID).And("sha = ?", sha).Find(&statuses)
}

// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
statuses := make([]*CommitStatus, 0, 10)
sess := x.NewSession()
defer sess.Close()

return statuses, sess.Limit(10, page*10).
Where("repo_id = ?", repo.ID).And("sha = ?", sha).Select("*").
GroupBy("context").Desc("created_unix").Find(&statuses)
}

// GetCommitStatus populates a given status for a given commit.
// NOTE: If ID or Index isn't given, and only Context, TargetURL and/or Description
// is given, the CommitStatus created _last_ will be returned.
func GetCommitStatus(repo *Repository, sha string, status *CommitStatus) (*CommitStatus, error) {
conds := &CommitStatus{
Context: status.Context,
State: status.State,
TargetURL: status.TargetURL,
Description: status.Description,
}
has, err := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha).Desc("created_unix").Get(conds)
if err != nil {
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: %v", repo.RepoPath(), sha, err)
}
if !has {
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: not found", repo.RepoPath(), sha)
}

return conds, nil
}

// NewCommitStatusOptions holds options for creating a CommitStatus
type NewCommitStatusOptions struct {
Repo *Repository
Creator *User
SHA string
CommitStatus *CommitStatus
}

func newCommitStatus(e Engine, opts NewCommitStatusOptions) error {
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

if opts.Repo == nil {
return fmt.Errorf("newCommitStatus[nil, %s]: no repository specified", opts.SHA)
}
opts.CommitStatus.RepoID = opts.Repo.ID

if opts.Creator == nil {
return fmt.Errorf("newCommitStatus[%s, %s]: no user specified", opts.Repo.RepoPath(), opts.SHA)
}

gitRepo, err := git.OpenRepository(opts.Repo.RepoPath())
if err != nil {
return fmt.Errorf("OpenRepository[%s]: %v", opts.Repo.RepoPath(), err)
}
if _, err := gitRepo.GetCommit(opts.SHA); err != nil {
return fmt.Errorf("GetCommit[%s]: %v", opts.SHA, err)
}

sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sess & Begin isn't required


// 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 {
sess.Rollback()
return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
}
if has {
log.Debug("newCommitStatus[%s, %s]: found", opts.Repo.RepoPath(), opts.SHA)
nextIndex = lastCommitStatus.Index
}
opts.CommitStatus.Index = nextIndex + 1
log.Debug("newCommitStatus[%s, %s]: %d", opts.Repo.RepoPath(), opts.SHA, opts.CommitStatus.Index)

// Insert new CommitStatus
if _, err = sess.Insert(opts.CommitStatus); err != nil {
sess.Rollback()
return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
}

return sess.Commit()
}

// NewCommitStatus creates a new CommitStatus given a bunch of parameters
// NOTE: All text-values will be trimmed from whitespaces.
// Requires: Repo, Creator, SHA
func NewCommitStatus(repo *Repository, creator *User, sha string, status *CommitStatus) error {
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", repo.ID, creator.ID, sha, err)
}

if err := newCommitStatus(sess, NewCommitStatusOptions{
Repo: repo,
Creator: creator,
SHA: sha,
CommitStatus: status,
}); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
}

return nil
}
Loading