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