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