| Index: build_scheduler/go/db/comments.go
|
| diff --git a/build_scheduler/go/db/comments.go b/build_scheduler/go/db/comments.go
|
| deleted file mode 100644
|
| index 85853a13e6c82cc5d9c585e115a6da687d45bb63..0000000000000000000000000000000000000000
|
| --- a/build_scheduler/go/db/comments.go
|
| +++ /dev/null
|
| @@ -1,500 +0,0 @@
|
| -package db
|
| -
|
| -import (
|
| - "bytes"
|
| - "encoding/gob"
|
| - "fmt"
|
| - "sync"
|
| - "time"
|
| -
|
| - "github.com/skia-dev/glog"
|
| - "go.skia.org/infra/go/util"
|
| -)
|
| -
|
| -// TaskComment contains a comment about a Task. {Repo, Name, Commit, Timestamp}
|
| -// is used as the unique id for this comment. If TaskId is empty, the comment
|
| -// applies to all matching tasks.
|
| -type TaskComment struct {
|
| - Repo string `json:"repo"`
|
| - Name string `json:"name"` // Name of TaskSpec.
|
| - Commit string `json:"commit"`
|
| - // Timestamp is compared ignoring timezone. The timezone reflects User's
|
| - // location.
|
| - Timestamp time.Time `json:"time"`
|
| - TaskId string `json:"taskId"`
|
| - User string `json:"user"`
|
| - Message string `json:"message"`
|
| -}
|
| -
|
| -func (c TaskComment) Copy() *TaskComment {
|
| - return &c
|
| -}
|
| -
|
| -// TaskSpecComment contains a comment about a TaskSpec. {Repo, Name, Timestamp}
|
| -// is used as the unique id for this comment.
|
| -type TaskSpecComment struct {
|
| - Repo string `json:"repo"`
|
| - Name string `json:"name"` // Name of TaskSpec.
|
| - // Timestamp is compared ignoring timezone. The timezone reflects User's
|
| - // location.
|
| - Timestamp time.Time `json:"time"`
|
| - User string `json:"user"`
|
| - Flaky bool `json:"flaky"`
|
| - IgnoreFailure bool `json:"ignoreFailure"`
|
| - Message string `json:"message"`
|
| -}
|
| -
|
| -func (c TaskSpecComment) Copy() *TaskSpecComment {
|
| - return &c
|
| -}
|
| -
|
| -// CommitComment contains a comment about a commit. {Repo, Commit, Timestamp} is
|
| -// used as the unique id for this comment.
|
| -type CommitComment struct {
|
| - Repo string `json:"repo"`
|
| - Commit string `json:"commit"`
|
| - // Timestamp is compared ignoring timezone. The timezone reflects User's
|
| - // location.
|
| - Timestamp time.Time `json:"time"`
|
| - User string `json:"user"`
|
| - Message string `json:"message"`
|
| -}
|
| -
|
| -func (c CommitComment) Copy() *CommitComment {
|
| - return &c
|
| -}
|
| -
|
| -// RepoComments contains comments that all pertain to the same repository.
|
| -type RepoComments struct {
|
| - // Repo is the repository (Repo field) of all the comments contained in
|
| - // this RepoComments.
|
| - Repo string
|
| - // TaskComments maps TaskSpec name and commit hash to the comments for
|
| - // the matching Task, sorted by timestamp.
|
| - TaskComments map[string]map[string][]*TaskComment
|
| - // TaskSpecComments maps TaskSpec name to the comments for that
|
| - // TaskSpec, sorted by timestamp.
|
| - TaskSpecComments map[string][]*TaskSpecComment
|
| - // CommitComments maps commit hash to the comments for that commit,
|
| - // sorted by timestamp.
|
| - CommitComments map[string][]*CommitComment
|
| -}
|
| -
|
| -func (orig *RepoComments) Copy() *RepoComments {
|
| - // TODO(benjaminwagner): Make this more efficient.
|
| - b := bytes.Buffer{}
|
| - if err := gob.NewEncoder(&b).Encode(orig); err != nil {
|
| - glog.Fatal(err)
|
| - }
|
| - copy := RepoComments{}
|
| - if err := gob.NewDecoder(&b).Decode(©); err != nil {
|
| - glog.Fatal(err)
|
| - }
|
| - return ©
|
| -}
|
| -
|
| -// CommentDB stores comments on Tasks, TaskSpecs, and commits.
|
| -//
|
| -// Clients must be tolerant of comments that refer to nonexistent Tasks,
|
| -// TaskSpecs, or commits.
|
| -type CommentDB interface {
|
| - // GetComments returns all comments for the given repos.
|
| - //
|
| - // If from is specified, it is a hint that TaskComments and CommitComments
|
| - // before this time will be ignored by the caller, thus they may be ommitted.
|
| - GetCommentsForRepos(repos []string, from time.Time) ([]*RepoComments, error)
|
| -
|
| - // PutTaskComment inserts the TaskComment into the database. May return
|
| - // ErrAlreadyExists.
|
| - PutTaskComment(*TaskComment) error
|
| -
|
| - // DeleteTaskComment deletes the matching TaskComment from the database.
|
| - // Non-ID fields of the argument are ignored.
|
| - DeleteTaskComment(*TaskComment) error
|
| -
|
| - // PutTaskSpecComment inserts the TaskSpecComment into the database. May
|
| - // return ErrAlreadyExists.
|
| - PutTaskSpecComment(*TaskSpecComment) error
|
| -
|
| - // DeleteTaskSpecComment deletes the matching TaskSpecComment from the
|
| - // database. Non-ID fields of the argument are ignored.
|
| - DeleteTaskSpecComment(*TaskSpecComment) error
|
| -
|
| - // PutCommitComment inserts the CommitComment into the database. May return
|
| - // ErrAlreadyExists.
|
| - PutCommitComment(*CommitComment) error
|
| -
|
| - // DeleteCommitComment deletes the matching CommitComment from the database.
|
| - // Non-ID fields of the argument are ignored.
|
| - DeleteCommitComment(*CommitComment) error
|
| -}
|
| -
|
| -// CommentBox implements CommentDB with in-memory storage.
|
| -//
|
| -// When created via NewCommentBoxWithPersistence, CommentBox will persist the
|
| -// in-memory representation on every change using the provided writer function.
|
| -//
|
| -// CommentBox can be default-initialized if only in-memory storage is desired.
|
| -type CommentBox struct {
|
| - // mtx protects comments.
|
| - mtx sync.RWMutex
|
| - // comments is map[repo_name]*RepoComments.
|
| - comments map[string]*RepoComments
|
| - // writer is called to persist comments after every change.
|
| - writer func(map[string]*RepoComments) error
|
| -}
|
| -
|
| -// NewCommentBoxWithPersistence creates a CommentBox that is initialized with
|
| -// init and sends the updated in-memory representation to writer after each
|
| -// change. The value of init and the argument to writer is
|
| -// map[repo_name]*RepoComments. init must not be modified by the caller. writer
|
| -// must not call any methods of CommentBox. writer may return an error to
|
| -// prevent a change from taking effect.
|
| -func NewCommentBoxWithPersistence(init map[string]*RepoComments, writer func(map[string]*RepoComments) error) *CommentBox {
|
| - return &CommentBox{
|
| - comments: init,
|
| - writer: writer,
|
| - }
|
| -}
|
| -
|
| -// See documentation for CommentDB.GetCommentsForRepos.
|
| -func (b *CommentBox) GetCommentsForRepos(repos []string, from time.Time) ([]*RepoComments, error) {
|
| - b.mtx.RLock()
|
| - defer b.mtx.RUnlock()
|
| - rv := make([]*RepoComments, len(repos))
|
| - for i, repo := range repos {
|
| - if rc, ok := b.comments[repo]; ok {
|
| - rv[i] = rc.Copy()
|
| - } else {
|
| - rv[i] = &RepoComments{Repo: repo}
|
| - }
|
| - }
|
| - return rv, nil
|
| -}
|
| -
|
| -// write calls b.writer with comments if non-null.
|
| -func (b *CommentBox) write() error {
|
| - if b.writer == nil {
|
| - return nil
|
| - }
|
| - return b.writer(b.comments)
|
| -}
|
| -
|
| -// getRepoComments returns the initialized *RepoComments for the given repo.
|
| -func (b *CommentBox) getRepoComments(repo string) *RepoComments {
|
| - if b.comments == nil {
|
| - b.comments = make(map[string]*RepoComments, 1)
|
| - }
|
| - rc, ok := b.comments[repo]
|
| - if !ok {
|
| - rc = &RepoComments{
|
| - Repo: repo,
|
| - TaskComments: map[string]map[string][]*TaskComment{},
|
| - TaskSpecComments: map[string][]*TaskSpecComment{},
|
| - CommitComments: map[string][]*CommitComment{},
|
| - }
|
| - b.comments[repo] = rc
|
| - }
|
| - return rc
|
| -}
|
| -
|
| -// putTaskComment validates c and adds c to b.comments, or returns
|
| -// ErrAlreadyExists if a different comment has the same ID fields. Assumes b.mtx
|
| -// is write-locked.
|
| -func (b *CommentBox) putTaskComment(c *TaskComment) error {
|
| - if c.Repo == "" || c.Name == "" || c.Commit == "" || util.TimeIsZero(c.Timestamp) {
|
| - return fmt.Errorf("TaskComment missing required fields. %#v", c)
|
| - }
|
| - rc := b.getRepoComments(c.Repo)
|
| - commitMap, ok := rc.TaskComments[c.Name]
|
| - if !ok {
|
| - commitMap = map[string][]*TaskComment{}
|
| - rc.TaskComments[c.Name] = commitMap
|
| - }
|
| - cSlice := commitMap[c.Commit]
|
| - // TODO(benjaminwagner): Would using utilities in the sort package make this
|
| - // cleaner?
|
| - if len(cSlice) > 0 {
|
| - // Assume comments normally inserted at the end.
|
| - insert := 0
|
| - for i := len(cSlice) - 1; i >= 0; i-- {
|
| - if cSlice[i].Timestamp.Equal(c.Timestamp) {
|
| - if *cSlice[i] == *c {
|
| - return nil
|
| - } else {
|
| - return ErrAlreadyExists
|
| - }
|
| - } else if cSlice[i].Timestamp.Before(c.Timestamp) {
|
| - insert = i + 1
|
| - break
|
| - }
|
| - }
|
| - // Ensure capacity for another comment and move any comments after the
|
| - // insertion point.
|
| - cSlice = append(cSlice, nil)
|
| - copy(cSlice[insert+1:], cSlice[insert:])
|
| - cSlice[insert] = c.Copy()
|
| - } else {
|
| - cSlice = []*TaskComment{c.Copy()}
|
| - }
|
| - commitMap[c.Commit] = cSlice
|
| - return nil
|
| -}
|
| -
|
| -// deleteTaskComment validates c, then finds and removes a comment matching c's
|
| -// ID fields, returning the comment if found. Assumes b.mtx is write-locked.
|
| -func (b *CommentBox) deleteTaskComment(c *TaskComment) (*TaskComment, error) {
|
| - if c.Repo == "" || c.Name == "" || c.Commit == "" || util.TimeIsZero(c.Timestamp) {
|
| - return nil, fmt.Errorf("TaskComment missing required fields. %#v", c)
|
| - }
|
| - if rc, ok := b.comments[c.Repo]; ok {
|
| - if cSlice, ok := rc.TaskComments[c.Name][c.Commit]; ok {
|
| - // Assume linear search is fast.
|
| - for i, existing := range cSlice {
|
| - if existing.Timestamp.Equal(c.Timestamp) {
|
| - if len(cSlice) > 1 {
|
| - rc.TaskComments[c.Name][c.Commit] = append(cSlice[:i], cSlice[i+1:]...)
|
| - } else {
|
| - delete(rc.TaskComments[c.Name], c.Commit)
|
| - if len(rc.TaskComments[c.Name]) == 0 {
|
| - delete(rc.TaskComments, c.Name)
|
| - }
|
| - }
|
| - return existing, nil
|
| - }
|
| - }
|
| - }
|
| - }
|
| - return nil, nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.PutTaskComment.
|
| -func (b *CommentBox) PutTaskComment(c *TaskComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - if err := b.putTaskComment(c); err != nil {
|
| - return err
|
| - }
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if _, delErr := b.deleteTaskComment(c); delErr != nil {
|
| - glog.Warning("Unexpected error: %s", delErr)
|
| - }
|
| - return err
|
| - }
|
| - return nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.DeleteTaskComment.
|
| -func (b *CommentBox) DeleteTaskComment(c *TaskComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - existing, err := b.deleteTaskComment(c)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - if existing != nil {
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if putErr := b.putTaskComment(existing); putErr != nil {
|
| - glog.Warning("Unexpected error: %s", putErr)
|
| - }
|
| - return err
|
| - }
|
| - }
|
| - return nil
|
| -}
|
| -
|
| -// putTaskSpecComment validates c and adds c to b.comments, or returns
|
| -// ErrAlreadyExists if a different comment has the same ID fields. Assumes b.mtx
|
| -// is write-locked.
|
| -func (b *CommentBox) putTaskSpecComment(c *TaskSpecComment) error {
|
| - if c.Repo == "" || c.Name == "" || util.TimeIsZero(c.Timestamp) {
|
| - return fmt.Errorf("TaskSpecComment missing required fields. %#v", c)
|
| - }
|
| - rc := b.getRepoComments(c.Repo)
|
| - cSlice := rc.TaskSpecComments[c.Name]
|
| - if len(cSlice) > 0 {
|
| - // Assume comments normally inserted at the end.
|
| - insert := 0
|
| - for i := len(cSlice) - 1; i >= 0; i-- {
|
| - if cSlice[i].Timestamp.Equal(c.Timestamp) {
|
| - if *cSlice[i] == *c {
|
| - return nil
|
| - } else {
|
| - return ErrAlreadyExists
|
| - }
|
| - } else if cSlice[i].Timestamp.Before(c.Timestamp) {
|
| - insert = i + 1
|
| - break
|
| - }
|
| - }
|
| - // Ensure capacity for another comment and move any comments after the
|
| - // insertion point.
|
| - cSlice = append(cSlice, nil)
|
| - copy(cSlice[insert+1:], cSlice[insert:])
|
| - cSlice[insert] = c.Copy()
|
| - } else {
|
| - cSlice = []*TaskSpecComment{c.Copy()}
|
| - }
|
| - rc.TaskSpecComments[c.Name] = cSlice
|
| - return nil
|
| -}
|
| -
|
| -// deleteTaskSpecComment validates c, then finds and removes a comment matching
|
| -// c's ID fields, returning the comment if found. Assumes b.mtx is write-locked.
|
| -func (b *CommentBox) deleteTaskSpecComment(c *TaskSpecComment) (*TaskSpecComment, error) {
|
| - if c.Repo == "" || c.Name == "" || util.TimeIsZero(c.Timestamp) {
|
| - return nil, fmt.Errorf("TaskSpecComment missing required fields. %#v", c)
|
| - }
|
| - if rc, ok := b.comments[c.Repo]; ok {
|
| - if cSlice, ok := rc.TaskSpecComments[c.Name]; ok {
|
| - // Assume linear search is fast.
|
| - for i, existing := range cSlice {
|
| - if existing.Timestamp.Equal(c.Timestamp) {
|
| - if len(cSlice) > 1 {
|
| - rc.TaskSpecComments[c.Name] = append(cSlice[:i], cSlice[i+1:]...)
|
| - } else {
|
| - delete(rc.TaskSpecComments, c.Name)
|
| - }
|
| - return existing, nil
|
| - }
|
| - }
|
| - }
|
| - }
|
| - return nil, nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.PutTaskSpecComment.
|
| -func (b *CommentBox) PutTaskSpecComment(c *TaskSpecComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - if err := b.putTaskSpecComment(c); err != nil {
|
| - return err
|
| - }
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if _, delErr := b.deleteTaskSpecComment(c); delErr != nil {
|
| - glog.Warning("Unexpected error: %s", delErr)
|
| - }
|
| - return err
|
| - }
|
| - return nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.DeleteTaskSpecComment.
|
| -func (b *CommentBox) DeleteTaskSpecComment(c *TaskSpecComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - existing, err := b.deleteTaskSpecComment(c)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - if existing != nil {
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if putErr := b.putTaskSpecComment(existing); putErr != nil {
|
| - glog.Warning("Unexpected error: %s", putErr)
|
| - }
|
| - return err
|
| - }
|
| - }
|
| - return nil
|
| -}
|
| -
|
| -// putCommitComment validates c and adds c to b.comments, or returns
|
| -// ErrAlreadyExists if a different comment has the same ID fields. Assumes b.mtx
|
| -// is write-locked.
|
| -func (b *CommentBox) putCommitComment(c *CommitComment) error {
|
| - if c.Repo == "" || c.Commit == "" || util.TimeIsZero(c.Timestamp) {
|
| - return fmt.Errorf("CommitComment missing required fields. %#v", c)
|
| - }
|
| - rc := b.getRepoComments(c.Repo)
|
| - cSlice := rc.CommitComments[c.Commit]
|
| - if len(cSlice) > 0 {
|
| - // Assume comments normally inserted at the end.
|
| - insert := 0
|
| - for i := len(cSlice) - 1; i >= 0; i-- {
|
| - if cSlice[i].Timestamp.Equal(c.Timestamp) {
|
| - if *cSlice[i] == *c {
|
| - return nil
|
| - } else {
|
| - return ErrAlreadyExists
|
| - }
|
| - } else if cSlice[i].Timestamp.Before(c.Timestamp) {
|
| - insert = i + 1
|
| - break
|
| - }
|
| - }
|
| - // Ensure capacity for another comment and move any comments after the
|
| - // insertion point.
|
| - cSlice = append(cSlice, nil)
|
| - copy(cSlice[insert+1:], cSlice[insert:])
|
| - cSlice[insert] = c.Copy()
|
| - } else {
|
| - cSlice = []*CommitComment{c.Copy()}
|
| - }
|
| - rc.CommitComments[c.Commit] = cSlice
|
| - return nil
|
| -}
|
| -
|
| -// deleteCommitComment validates c, then finds and removes a comment matching
|
| -// c's ID fields, returning the comment if found. Assumes b.mtx is write-locked.
|
| -func (b *CommentBox) deleteCommitComment(c *CommitComment) (*CommitComment, error) {
|
| - if c.Repo == "" || c.Commit == "" || util.TimeIsZero(c.Timestamp) {
|
| - return nil, fmt.Errorf("CommitComment missing required fields. %#v", c)
|
| - }
|
| - if rc, ok := b.comments[c.Repo]; ok {
|
| - if cSlice, ok := rc.CommitComments[c.Commit]; ok {
|
| - // Assume linear search is fast.
|
| - for i, existing := range cSlice {
|
| - if existing.Timestamp.Equal(c.Timestamp) {
|
| - if len(cSlice) > 1 {
|
| - rc.CommitComments[c.Commit] = append(cSlice[:i], cSlice[i+1:]...)
|
| - } else {
|
| - delete(rc.CommitComments, c.Commit)
|
| - }
|
| - return existing, nil
|
| - }
|
| - }
|
| - }
|
| - }
|
| - return nil, nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.PutCommitComment.
|
| -func (b *CommentBox) PutCommitComment(c *CommitComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - if err := b.putCommitComment(c); err != nil {
|
| - return err
|
| - }
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if _, delErr := b.deleteCommitComment(c); delErr != nil {
|
| - glog.Warning("Unexpected error: %s", delErr)
|
| - }
|
| - return err
|
| - }
|
| - return nil
|
| -}
|
| -
|
| -// See documentation for CommentDB.DeleteCommitComment.
|
| -func (b *CommentBox) DeleteCommitComment(c *CommitComment) error {
|
| - b.mtx.Lock()
|
| - defer b.mtx.Unlock()
|
| - existing, err := b.deleteCommitComment(c)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - if existing != nil {
|
| - if err := b.write(); err != nil {
|
| - // If write returns an error, we must revert to previous.
|
| - if putErr := b.putCommitComment(existing); putErr != nil {
|
| - glog.Warning("Unexpected error: %s", putErr)
|
| - }
|
| - return err
|
| - }
|
| - }
|
| - return nil
|
| -}
|
|
|