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