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