bug.go

  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/identity"
  8	"github.com/MichaelMure/git-bug/entity"
  9	"github.com/MichaelMure/git-bug/entity/dag"
 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
 45func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
 46	return entity.Resolvers{
 47		&identity.Identity{}: identity.NewSimpleResolver(repo),
 48	}
 49}
 50
 51// Read will read a bug from a repository
 52func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
 53	return ReadWithResolver(repo, simpleResolvers(repo), id)
 54}
 55
 56// ReadWithResolver will read a bug from its Id, with custom resolvers
 57func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Bug, error) {
 58	e, err := dag.Read(def, repo, resolvers, id)
 59	if err != nil {
 60		return nil, err
 61	}
 62	return &Bug{Entity: e}, nil
 63}
 64
 65type StreamedBug struct {
 66	Bug *Bug
 67	Err error
 68}
 69
 70// ReadAll read and parse all local bugs
 71func ReadAll(repo repository.ClockedRepo) <-chan StreamedBug {
 72	return readAll(repo, simpleResolvers(repo))
 73}
 74
 75// ReadAllWithResolver read and parse all local bugs
 76func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
 77	return readAll(repo, resolvers)
 78}
 79
 80// Read and parse all available bug with a given ref prefix
 81func readAll(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedBug {
 82	out := make(chan StreamedBug)
 83
 84	go func() {
 85		defer close(out)
 86
 87		for streamedEntity := range dag.ReadAll(def, repo, resolvers) {
 88			if streamedEntity.Err != nil {
 89				out <- StreamedBug{
 90					Err: streamedEntity.Err,
 91				}
 92			} else {
 93				out <- StreamedBug{
 94					Bug: &Bug{Entity: streamedEntity.Entity},
 95				}
 96			}
 97		}
 98	}()
 99
100	return out
101}
102
103// ListLocalIds list all the available local bug ids
104func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
105	return dag.ListLocalIds(def, repo)
106}
107
108// Validate check if the Bug data is valid
109func (bug *Bug) Validate() error {
110	if err := bug.Entity.Validate(); err != nil {
111		return err
112	}
113
114	// The very first Op should be a CreateOp
115	firstOp := bug.FirstOp()
116	if firstOp == nil || firstOp.Type() != CreateOp {
117		return fmt.Errorf("first operation should be a Create op")
118	}
119
120	// Check that there is no more CreateOp op
121	for i, op := range bug.Operations() {
122		if i == 0 {
123			continue
124		}
125		if op.Type() == CreateOp {
126			return fmt.Errorf("only one Create op allowed")
127		}
128	}
129
130	return nil
131}
132
133// Append add a new Operation to the Bug
134func (bug *Bug) Append(op Operation) {
135	bug.Entity.Append(op)
136}
137
138// Operations return the ordered operations
139func (bug *Bug) Operations() []Operation {
140	source := bug.Entity.Operations()
141	result := make([]Operation, len(source))
142	for i, op := range source {
143		result[i] = op.(Operation)
144	}
145	return result
146}
147
148// Compile a bug in a easily usable snapshot
149func (bug *Bug) Compile() *Snapshot {
150	snap := &Snapshot{
151		id:     bug.Id(),
152		Status: OpenStatus,
153	}
154
155	for _, op := range bug.Operations() {
156		op.Apply(snap)
157		snap.Operations = append(snap.Operations, op)
158	}
159
160	return snap
161}
162
163// FirstOp lookup for the very first operation of the bug.
164// For a valid Bug, this operation should be a CreateOp
165func (bug *Bug) FirstOp() Operation {
166	if fo := bug.Entity.FirstOp(); fo != nil {
167		return fo.(Operation)
168	}
169	return nil
170}
171
172// LastOp lookup for the very last operation of the bug.
173// For a valid Bug, should never be nil
174func (bug *Bug) LastOp() Operation {
175	if lo := bug.Entity.LastOp(); lo != nil {
176		return lo.(Operation)
177	}
178	return nil
179}