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