1// Package bug contains the bug data model and low-level related functions
  2package bug
  3
  4import (
  5	"fmt"
  6
  7	"github.com/MichaelMure/git-bug/entities/common"
  8	"github.com/MichaelMure/git-bug/entities/identity"
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/entity/dag"
 11	"github.com/MichaelMure/git-bug/repository"
 12)
 13
 14var _ Interface = &Bug{}
 15var _ entity.Interface = &Bug{}
 16
 17// 1: original format
 18// 2: no more legacy identities
 19// 3: Ids are generated from the create operation serialized data instead of from the first git commit
 20// 4: with DAG entity framework
 21const formatVersion = 4
 22
 23var def = dag.Definition{
 24	Typename:             "bug",
 25	Namespace:            "bugs",
 26	OperationUnmarshaler: operationUnmarshaler,
 27	FormatVersion:        formatVersion,
 28}
 29
 30var ClockLoader = dag.ClockLoader(def)
 31
 32type Interface interface {
 33	dag.Interface[*Snapshot, Operation]
 34}
 35
 36// Bug holds the data of a bug thread, organized in a way close to
 37// how it will be persisted inside Git. This is the data structure
 38// used to merge two different version of the same Bug.
 39type Bug struct {
 40	*dag.Entity
 41}
 42
 43// NewBug create a new Bug
 44func NewBug() *Bug {
 45	return &Bug{
 46		Entity: dag.New(def),
 47	}
 48}
 49
 50func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
 51	return entity.Resolvers{
 52		&identity.Identity{}: identity.NewSimpleResolver(repo),
 53	}
 54}
 55
 56// Read will read a bug from a repository
 57func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
 58	return ReadWithResolver(repo, simpleResolvers(repo), id)
 59}
 60
 61// ReadWithResolver will read a bug from its Id, with custom resolvers
 62func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Bug, error) {
 63	e, err := dag.Read(def, repo, resolvers, id)
 64	if err != nil {
 65		return nil, err
 66	}
 67	return &Bug{Entity: e}, nil
 68}
 69
 70type StreamedBug struct {
 71	Bug *Bug
 72	Err error
 73}
 74
 75// ReadAll read and parse all local bugs
 76func ReadAll(repo repository.ClockedRepo) <-chan StreamedBug {
 77	return readAll(repo, simpleResolvers(repo))
 78}
 79
 80// ReadAllWithResolver read and parse all local bugs
 81func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
 82	return readAll(repo, resolvers)
 83}
 84
 85// Read and parse all available bug with a given ref prefix
 86func readAll(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
 87	out := make(chan StreamedBug)
 88
 89	go func() {
 90		defer close(out)
 91
 92		for streamedEntity := range dag.ReadAll(def, repo, resolvers) {
 93			if streamedEntity.Err != nil {
 94				out <- StreamedBug{
 95					Err: streamedEntity.Err,
 96				}
 97			} else {
 98				out <- StreamedBug{
 99					Bug: &Bug{Entity: streamedEntity.Entity},
100				}
101			}
102		}
103	}()
104
105	return out
106}
107
108// ListLocalIds list all the available local bug ids
109func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
110	return dag.ListLocalIds(def, repo)
111}
112
113// Validate check if the Bug data is valid
114func (bug *Bug) Validate() error {
115	if err := bug.Entity.Validate(); err != nil {
116		return err
117	}
118
119	// The very first Op should be a CreateOp
120	firstOp := bug.FirstOp()
121	if firstOp == nil || firstOp.Type() != CreateOp {
122		return fmt.Errorf("first operation should be a Create op")
123	}
124
125	// Check that there is no more CreateOp op
126	for i, op := range bug.Entity.Operations() {
127		if i == 0 {
128			continue
129		}
130		if op.Type() == CreateOp {
131			return fmt.Errorf("only one Create op allowed")
132		}
133	}
134
135	return nil
136}
137
138// Append add a new Operation to the Bug
139func (bug *Bug) Append(op Operation) {
140	bug.Entity.Append(op)
141}
142
143// Operations return the ordered operations
144func (bug *Bug) Operations() []Operation {
145	source := bug.Entity.Operations()
146	result := make([]Operation, len(source))
147	for i, op := range source {
148		result[i] = op.(Operation)
149	}
150	return result
151}
152
153// Compile a bug in an easily usable snapshot
154func (bug *Bug) Compile() *Snapshot {
155	snap := &Snapshot{
156		id:     bug.Id(),
157		Status: common.OpenStatus,
158	}
159
160	for _, op := range bug.Operations() {
161		op.Apply(snap)
162		snap.Operations = append(snap.Operations, op)
163	}
164
165	return snap
166}
167
168// FirstOp lookup for the very first operation of the bug.
169// For a valid Bug, this operation should be a CreateOp
170func (bug *Bug) FirstOp() Operation {
171	if fo := bug.Entity.FirstOp(); fo != nil {
172		return fo.(Operation)
173	}
174	return nil
175}
176
177// LastOp lookup for the very last operation of the bug.
178// For a valid Bug, should never be nil
179func (bug *Bug) LastOp() Operation {
180	if lo := bug.Entity.LastOp(); lo != nil {
181		return lo.(Operation)
182	}
183	return nil
184}