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