// Package bug contains the bug data model and low-level related functions
package bug
import (
"fmt"
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
)
var _ Interface = &Bug{}
var _ entity.Interface = &Bug{}
// 1: original format
// 2: no more legacy identities
// 3: Ids are generated from the create operation serialized data instead of from the first git commit
// 4: with DAG entity framework
const formatVersion = 4
var def = dag.Definition{
Typename: "bug",
Namespace: "bugs",
OperationUnmarshaler: operationUnmarshaller,
FormatVersion: formatVersion,
}
var ClockLoader = dag.ClockLoader(def)
// Bug holds the data of a bug thread, organized in a way close to
// how it will be persisted inside Git. This is the data structure
// used to merge two different version of the same Bug.
type Bug struct {
*dag.Entity
}
// NewBug create a new Bug
func NewBug() *Bug {
return &Bug{
Entity: dag.New(def),
}
}
func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
return entity.Resolvers{
&identity.Identity{}: identity.NewSimpleResolver(repo),
}
}
// Read will read a bug from a repository
func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
return ReadWithResolver(repo, simpleResolvers(repo), id)
}
// ReadWithResolver will read a bug from its Id, with custom resolvers
func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Bug, error) {
e, err := dag.Read(def, repo, resolvers, id)
if err != nil {
return nil, err
}
return &Bug{Entity: e}, nil
}
type StreamedBug struct {
Bug *Bug
Err error
}
// ReadAll read and parse all local bugs
func ReadAll(repo repository.ClockedRepo) <-chan StreamedBug {
return readAll(repo, simpleResolvers(repo))
}
// ReadAllWithResolver read and parse all local bugs
func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
return readAll(repo, resolvers)
}
// Read and parse all available bug with a given ref prefix
func readAll(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
out := make(chan StreamedBug)
go func() {
defer close(out)
for streamedEntity := range dag.ReadAll(def, repo, resolvers) {
if streamedEntity.Err != nil {
out <- StreamedBug{
Err: streamedEntity.Err,
}
} else {
out <- StreamedBug{
Bug: &Bug{Entity: streamedEntity.Entity},
}
}
}
}()
return out
}
// ListLocalIds list all the available local bug ids
func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
return dag.ListLocalIds(def, repo)
}
// Validate check if the Bug data is valid
func (bug *Bug) Validate() error {
if err := bug.Entity.Validate(); err != nil {
return err
}
// The very first Op should be a CreateOp
firstOp := bug.FirstOp()
if firstOp == nil || firstOp.Type() != CreateOp {
return fmt.Errorf("first operation should be a Create op")
}
// Check that there is no more CreateOp op
for i, op := range bug.Operations() {
if i == 0 {
continue
}
if op.Type() == CreateOp {
return fmt.Errorf("only one Create op allowed")
}
}
return nil
}
// Append add a new Operation to the Bug
func (bug *Bug) Append(op Operation) {
bug.Entity.Append(op)
}
// Operations return the ordered operations
func (bug *Bug) Operations() []Operation {
source := bug.Entity.Operations()
result := make([]Operation, len(source))
for i, op := range source {
result[i] = op.(Operation)
}
return result
}
// Compile a bug in a easily usable snapshot
func (bug *Bug) Compile() *Snapshot {
snap := &Snapshot{
id: bug.Id(),
Status: OpenStatus,
}
for _, op := range bug.Operations() {
op.Apply(snap)
snap.Operations = append(snap.Operations, op)
}
return snap
}
// FirstOp lookup for the very first operation of the bug.
// For a valid Bug, this operation should be a CreateOp
func (bug *Bug) FirstOp() Operation {
if fo := bug.Entity.FirstOp(); fo != nil {
return fo.(Operation)
}
return nil
}
// LastOp lookup for the very last operation of the bug.
// For a valid Bug, should never be nil
func (bug *Bug) LastOp() Operation {
if lo := bug.Entity.LastOp(); lo != nil {
return lo.(Operation)
}
return nil
}